C语言实现常见排序算法的(直接插入排序,希尔排序,堆排序,选择排序,冒泡排序,快速排序,归并排序)

目录

1.直接插入排序 

2.希尔排序

3.堆排序

向下调整算法

建堆

4.选择排序

5.冒泡排序

6.快速排序

7.归并排序


1.直接插入排序 


void Insertsort(int* a,int n)
{
	int end = 0;
	//这里的i控制每次比较最多要执行多少次
	for(int i = 0;i < n-1;i++)
	{
		//end保存当前位置
		end = i;
		//tmp保存要插入排序的数
		//[0,end]有序,插入a[end+1],使得[0,end+1]有序
		int tmp = a[end + 1];
		while (end >= 0)
		{
		   //当a[end] > tmp时,就要往后移一位
			if (a[end] > tmp)
			{
				a[end + 1] = a[end];
				//赋值后end--,取得下一个比较数
				end--;
			}
			else
			{
				//因为排序是从都一个元素开始的,所以当a[end] =/< tmp时即代表不用排序
				break;
			}
		}
		//无论哪种结束将tmp保存end加一的位置
		a[end + 1] = tmp;
	}
}

思想:直接插入排序的思想就是从end+1的位置(也就是第二个元素的位置)往后比较,如果后一个元素比前一个元素大,就把后一个元素往前移,因为是从第二个元素的位置开始,所以当执行到else时,即代表后面有序,所以无论是在if中停止还是在else中停止,最后的a[end+1] 都是key保存的正确位置(因为end放的第一个元素的下标,进入if代表往后移了,往前移end就--,那么end就是被移动的元素的位置,座椅所以加一是正确位置)。

2.希尔排序

void shellsort(int* a, int n)
{
	int end = 0;
	int gap = n;
	int tmp = 0;
	//当gap等于一时,为直接插入排序
	while(gap > 1)
	{
		//每一趟gap/2,这里gap/3也可以,但是要加1,因为/3有可能到不了一
		gap = gap / 2;
		//这里第一个元素开始先排序,排序条件要控制为小于n-gap因为大于这个值end加gap就会导致数组越界访问
		for (int i = 0; i < n - gap; i++)
		{
			//end为比较的起始位置
			end = i;
			//要比较的元素
			tmp = a[end + gap];
			while (end >= 0)
			{
				//如果tmp < a[end]就将end的元素换到end+gap上,然后end-=gap,去到下一个比较的位置,此时end再加gap就是上一个位置和下一个位置的比较
				if (tmp < a[end])
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				//如果遇到等于大于的就不用交换,因为我们从零开始的,所以直接break掉就可以了
				else
				{
					break;
				}
			}
			//最后将tmp保存在end+gap的位置,因为上面又可能一次交换后就直接走到break,也可能再-=gap才结束循环,所以en+gap是tmp这趟排序的正确位置
			a[end + gap] = tmp;
		}
	}
}

原理:希尔排序和直接插入排序很像,直接插入排序时往后面的元素一个一个的比,希尔排序就是往后gap个比,所以当gap=1时,希尔排序就是直接插入排序,当gap逐渐到一的过程,整个数组就比较趋于有序(大的换到右边,小的换到左边)

3.堆排序

void Swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
void adjustdown(int* a, int root, int n)
{
	//父亲节点
	int parent = root;
	//孩子节点,默认是左孩子结点
	int child = (parent * 2) + 1;
	//当孩子节点不超过数组的元素个数时,循环可以继续
	while (child < n)
	{
		//child+为右节点,比较左右节点找出大的那一个
		if (child + 1 < n  && a[child] < a[child + 1])
		{
			child += 1;
		}
		//比较父节点与较大的孩子节点,满足条件交换
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			//交换后父节点来到了子节点这里,子节点则需要重新计算
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			//如果a[child] < a[parent]直接结束即可,因为我们是从最下面排上来的,如果已经符合了那么下面一定符合大堆
			break;
		}
	}
}

void heapsort(int *a,int n)
{
	//这里传递的为元素的下标,所以公式为parent = (child-1)/2计算最后一个元素的的父亲,所以应当是n-1才是其下标
	int parent = (n - 1-1) / 2;
	for (int i = parent; i >= 0; i--)
	{
		//这里parent就是最后一个有子节点的父节点,因为本质是数组,所以--就是剩下的父节点
		adjustdown(a, i, n);
	}
	int end = n - 1;
	//当还剩最后一个元素就停止循环
	while (end > 0)
	{
		//n-1为最后一个元素下标,排大堆将首尾交换,在向下调整
		Swap(&a[0], &a[end]);
		//这里的end是元素个数
		adjustdown(a, 0, end);
		--end;
	}
}

  • 堆排序的前提是左右子树得是大堆或小堆
  • 堆的逻辑结构是一颗完全二叉树,物理结构是一个数组
  • 通过数组下标计算父子结点的公式:

        leftchild = parent*2+1

        rightchild = parent*2+2

        parent= (child-1)/2

向下调整算法

void Swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
void adjustdown(int* a, int root, int n)
{
	//父亲节点
	int parent = root;
	//孩子节点,默认是左孩子结点
	int child = (parent * 2) + 1;
	//当孩子节点不超过数组的元素个数时,循环可以继续
	while (child < n)
	{
		//child+为右节点,比较左右节点找出大的那一个
		if (child + 1 < n  && a[child] < a[child + 1])
		{
			child += 1;
		}
		//比较父节点与较大的孩子节点,满足条件交换
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			//交换后父节点来到了子节点这里,子节点则需要重新计算
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			//如果a[child] < a[parent]直接结束即可,因为我们是从最下面排上来的,如果已经符合了那么下面一定符合大堆
			break;
		}
	}
}

堆排序要用到向下调整算法,可以选择建大堆或者建小堆,但排升序建议建大堆,因为建小堆最小值在最上面,想要升序堆的结构会改变,需要重新建堆,而建大堆不需要重新建堆,交换在控制坐标即可。上面是建大堆过程,根据坐标计算公式,计算出左右孩子,选出较大的与父节点交换,然后再调整坐标。

建堆


void heapsort(int *a,int n)
{
	//这里传递的为元素的下标,所以公式为parent = (child-1)/2计算最后一个元素的的父亲,所以应当是n-1才是其下标
	int parent = (n - 1-1) / 2;
	for (int i = parent; i >= 0; i--)
	{
		//这里parent就是最后一个有子节点的父节点,因为本质是数组,所以--就是剩下的父节点
		adjustdown(a, i, n);
	}
	int end = n - 1;
	//当还剩最后一个元素就停止循环
	while (end > 0)
	{
		//n-1为最后一个元素下标,排大堆将首尾交换,在向下调整
		Swap(&a[0], &a[end]);
		//这里的end是元素个数
		adjustdown(a, 0, end);
		--end;
	}
}

建堆我么们考虑从最后一个叶子节点的父亲开始,因为堆的结构的逻辑结构是完全二叉树,所以找到其父亲在作为循环条件即可遍历全部的父亲,从下往上建堆。

建完堆后此时最大值就在最上面的位置,这个值的位置刚好是数组的最后一个元素所以将首尾元素交换,将尾坐标减减,然后再进行向下调整,这里的end是元素个数,因为上面向下调整的循环时<n及只遍历到n-1位,所以传递的end在是元素个数来看待,就有少一个的效果。

4.选择排序

void selectsort(int* a,int n)
{
	//初始位置
	int begin = 0;
	//结束位置
	int end = n - 1;
	//min保存最小值的下标,max保存最大值的下标
	int min = 0;
	int max = 0;
	while (begin < end)
	{
		//初始化为begin所在位置
		min = begin;
		max = begin;
		for (int i = begin; i <= end; i++)
		{
			//找小
			if (a[i] < a[min])
			{
				min = i;
			}
			//找大
			if (a[i] > a[max])
			{
				max = i;
			}
		}
		Swap(&a[begin], &a[min]);
		//这里要避免min要换的位置与max相同,及begin=max的情况,因为这样原本处于max的值就被换成min所指的值,然后min下标的值又被换到end
		if (begin == max)
		{
			max = min;
		}
		Swap(&a[max], &a[end]);
		begin++;
		end--;
	}
}

选择排序就是找出最大或者最小的放到指定位置,我这里采用了同时找大找小,放到首尾在控制坐标begin和end排除已经放好的位置。

5.冒泡排序

void bubblesort(int *a,int n)
{
	//这里的j决定趟数,第一趟决定最大值,第二趟决定次大值
	for (int j = 0; j < n; j++)
	{
		//这里-j刚好控制排序范围,因为一趟排序就确定一个值,这个值不需要排
		for (int i = 1; i < n - j; i++)
		{
			if (a[i - 1] > a[i])
			{
				Swap(&a[i - 1], &a[i]);
			}
		}
	}
}

冒泡排序应该是最简单得了,每一趟找到该趟范围内的最大值放到后面,这里就不多赘述,代码有注释。

6.快速排序


void Swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
//三数取中
int getmid(int* a, int left, int right)
{
	int mid = (left + right) >> 1;
	if (a[left] > a[mid])
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[right] > a[left])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
	else
	{
		if (a[left] > a[right])
		{
			return left;
		}
		else if (a[right] > a[mid])
		{
			return mid;
		}
		else
		{
			return right;
		}
	}
}
//快速排序
void quicksort(int* a, int left,int right)
{
	//如果只有一个元素或者没有元素就返回
	if (left >= right)
	{
		return;
	}
	//三数取中,取最小的那个
	int index = getmid(a, left, right);
	//这里除了key保存的是值,剩下三个都是下标
		//这里我们一般取第一个位置为坑,所以将上面下标的值与第一个下标的值交换即可
	Swap(&a[left], &a[index]);
	//这里right是元素最后的下标,不是元数个数,如果是元数个数每次进来递归进来每次都要减一,会导致递归排序的范围减少
	int begin = left;
	int end = right;
	int pivot = begin;
	//选第一个元素为key值
	int key = a[begin];
	while (begin < end)
	{
		//右边找小,a[end] >= key就继续,找到小的就停止,这个就是新的坑位
		//注意下面两个while都要加上判断条件begin < end,因为有可能在下面两个循环的过程中,当begin= end时,该位置的值还是>=key,导致end--,导致排序出错
		while (begin < end && a[end] >= key)
		{
			end--;
		}
		//找到小的给上一个坑位,并且形成新的坑位
		a[pivot] = a[end];
		pivot = end;
		//左边找大,a[begin] <= key就继续,找到大的就停止,这个就是新的坑位
		if (begin < end && a[begin] <= key)
		{
			begin++;
		}
		//找到大的给上一个坑位,并且形成新的坑位
		a[pivot] = a[begin];
		pivot = begin;
	}
	//这里当循环结束,begin = end 因为条件是begin<end才能进入,当begin = end 就会结束
	pivot = begin;
	a[begin] = key;
	//分治递归,pivot的左右两个区间
	quicksort(a, left, pivot - 1);
	quicksort(a, pivot + 1, right);
}

思想

  • 将第一个元素作为key值,左边找比key值大的,右边找比key值小的,找完key值就放到了合适的位置,然后将这个位置的左右位置进行递归
  • begin :初始下标,end:最后下标  ,pivot:坑位下标,key保存选出的值

7.归并排序

//归并排序
void _mergesort(int* a, int left, int right,int* tmp)
{
	//如果只有一个元素就不要递归了
	if (left >= right)
	{
		return;
	}
	//取分割点
	int mid = (left + right) >> 1;
	//递归
	_mergesort(a, left, mid, tmp);
	_mergesort(a, mid + 1, right, tmp);

	//四个变量存放归并的区间的范围
	int begin1 = left;
	int end1 = mid;
	int begin2 = mid + 1;
	int end2 = right;
	//记录临时数组的开始范围
	int index = left;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] < a[begin2])
		{
			tmp[index++] = a[begin1++];
		}
		else
		{
			tmp[index++] = a[begin2++];
		}
	}
	//归并可能出现乙方归并完成,但是另一方还没归并完成
	while (begin1 <= end1)
	{
		tmp[index++] = a[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[index++] = a[begin2++];
	}
	//拷贝回原数组
	for (int i = 0; i <= right; i++)
	{
		a[i] = tmp[i];
	}
}
void mergesort(int *a,int n)
{
	//如果在这个函数递归每次都要malloc一个数组,所以在上面创建一个用于递归的子函数
	int* tmp = (int*)malloc(sizeof(int) * n);
	_mergesort(a, 0, n - 1, tmp);
	free(tmp);
}

归并排序其实就是将整个数组分为两部分,再将这两部分递归又分别分成两部分,直到不可再分割,然后进行排序,根据调用传递的参数不同,将数据排好序放到开辟的临时数组中,再将其拷贝回原数组。这里注意不能直接排序放到原数组中,这样可能会导致覆盖。

大概就是这样子的思想。

  • 9
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值