八大经典排序算法(C语言)

插入排序

直接插入排序

基本思想: 把待排序的按其数值的大小,逐个插入到已有序序列,直到插入完为止。

直接插入排序动图:
插入排序动图
逻辑步骤:

逻辑步骤

实现:

void InsertSort(int* a, int n)
{
	for (int i = 1; i < n; i++)
	{
		//定义end和tmp,一前一后的关系
		int end = i - 1;
		int tmp = a[i];

		//循环的两个结束条件,end<0或者tmp>=a[end]
		while (end >= 0)
		{
			if (tmp < a[end])
			{
				a[end + 1] = a[end];
				end--;
			}
			else
			{
				break;
			}
		}
		
		//如果最初tmp>a[end],直接把tmp放到数组的最后即可
		a[end + 1] = tmp;
	}
}

希尔排序(缩小增量排序)

相当于插入排序的优化
基本思想: 把数据进行分组,把位置差异较大的值,快速靠近该值正确的位置

逻辑步骤:
逻辑步骤
实现:
思考:gap等于多少就是被分成多少组。

void ShellSort(int* a, int n)
{
	int gap = n;
	while (gap > 1)
	{
		//这样写的目的,为了提高效率,也可以写成gap = gap / 2;只要最后gap为1即可,为1的时候,插入排序过一遍
		gap = gap / 3 + 1;

		//注意循环的结束条件判断
		for (int i = 0; i < n - gap; i++)
		{
			//end和tmp的关系,变成每组的前后关系
			int end = i;
			int tmp = a[i + gap];
			
			while (end >= 0)
			{
				if (a[end] > tmp)
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			
			a[end + gap] = tmp;
		}
	}
}

选择排序

简单选择排序

基本思想: 每次从待排序的数组中选择最小的(最大的)一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。

简单选择排序动图:
选择排序动图

逻辑步骤:
逻辑步骤

实现:

void SelectSort(int* a, int n)
{
	int left = 0;
	int right = n - 1;

	while (left < right)
	{	
		//一次寻找最大最小两个值
		int mini = left;
		int maxi = left;

		//注意:该循环的起始、结束位置,不是0和n,因为已经排好的有序序列不需要再排
		for (int i = left + 1; i <= right; i++)
		{
			if (a[mini] > a[i])
			{
				mini = i;
			}

			if (a[maxi] < a[i])
			{
				maxi = i;
			}
		}

		//注意交换时候的特殊情况
		Swap(&a[mini], &a[left]);
		if (left == maxi)
		{
			maxi = mini;
		}
		
		Swap(&a[maxi], &a[right]);
		left++;
		right--;
	}
}

堆排序

之前写的堆排序的链接:堆排序

交换排序

冒泡排序

基本思想: 根据序列中两个数值的比较结果来对换这两个数值在序列中的位置。

冒泡排序动图:
冒泡排序动图

实现:

//交换
void Swap(int* x, int* y)
{
	int tmp = *x;
	*x = *y;
	*y = tmp;
}

void BubbleSort(int* a, int n)
{
	int i = 0;
	int j = 0;
	//趟数
	for (i = 0; i < n - 1; i++)
	{
		int flag = 0;

		//比较的次数
		for (j = 0; j < n - i - 1; j++)
		{
			//两两比较
			if (a[j] > a[j + 1])
			{
				Swap(&a[j], &a[j + 1]);
				flag = 1;
			}
		}
		
		//如果一趟循环过后一次没比较,也就代表该序列是有序的,直接结束循环-->属于冒泡循环的一种优化
		if (flag == 0)
			break;
	}
}

快速排序

快速排序的基本思想: 任取待排序序列中的某个元素作为基准值,按照该排序码将待排序集合分割成两个子序列,左子序列中所有元素均小于基准值,右子序列中的所有元素都大于基准值,然后递归该过程,直到所有元素都排列在相应的位置上。

在介绍快速排序的实现方法之前先介绍一下三数取中法。 (对快速排序的优化)
思想:在一个序列中取序列左右中三个数,比较三个数的大小,取中间值。

使用三数取中法,为了预防序列中的数据出现升序和降序的特殊情况,因为一个序列是升序和降序序列,使用快速排序效率低下。

三数取中法实现:

//三数取中法
int GetMidNum(int* a, int left, int right)
{
	int mid = (left + right) / 2;
	if (a[mid] > a[left])
	{
		if (a[left] > a[right])
		{
			return left;
		}
		else if (a[mid] > a[right])
		{
			return right;
		}
		else
		{
			return mid;
		}
	}
	else //a[mid] < a[left]
	{
		if (a[left] < a[right])
		{
			return left;
		}
		else if (a[right] > a[mid])
		{
			return right;
		}
		else
		{
			return mid;
		}
	}
}

快速排序的方法(递归)

hoare法

hoare法排序动图:
hoare法动图

逻辑步骤:

hoare法逻辑步骤
实现:

void HoareQuickSort(int* a, int left, int right)
{
	//判断递归结束的条件
	if (left >= right)
	{
		return;
	}

	//保存序列左右侧的值
	int end = right;
	int begin = left;


	//三数取中法
	int midi = GetMidNum(a, left, right);
	//把所取得数和最左侧的数交换,把交换后最左侧的数当作关键值
	if (midi != left)
		Swap(&a[midi], &a[left]);


	int keyi = left;
	while (begin < end)
	{
		while (begin < end && a[end] >= a[keyi])
		{
			end--;
		}

		while (begin < end && a[begin] <= a[keyi])
		{
			begin++;
		}

		Swap(&a[end], &a[begin]);
	}
	Swap(&a[keyi], &a[begin]);
	keyi = begin;

	//递归确定别的关键值的位置
	QuickSort(a, left, keyi - 1);
	QuickSort(a, keyi + 1, right);
}
挖坑法

挖坑法排序动图:
挖坑法动图
逻辑步骤:

挖坑法逻辑步骤

实现:

void HoleQuickSort(int* a, int left, int right)
{
	if (left >= right)
		return;
	//三数取中法
	int midi = GetMidNum(a, left, right);
	if (midi != left)
		Swap(&a[midi], &a[left]);

	int key = a[left];
	int hole = left;

	while (left < right)
	{
		while (left < right && a[right] >= key)
		{
			right--;
		}
		a[hole] = a[right];
		hole = right;


		while (left < right && a[left] <= key)
		{
			left++;
		}
		a[hole] = a[left];
		hole = left;
	}
	a[hole] = key;

	HoleQuickSort(a, left, hole - 1);
	HoleQuickSort(a, hole + 1, right);
}
前后指针法

前后指针法排序动图:
前后指针法动图
逻辑步骤:
前后指针法的逻辑步骤
实现:

void PointerQuickSort(int* a, int left, int right)
{
	if (left >= right)
		return;
		
	//三数取中法
	int midi = GetMidNum(a, left, right);
	if (midi != left)
		Swap(&a[midi], &a[left]);

	int keyi = left;
	int prev = left;
	int cur = left + 1;
	while (cur <= right)
	{
		//这个if语句完成了逻辑步骤中第一点的要求
		if (a[cur] < a[keyi] && ++prev != cur)
		{
			Swap(&a[prev], &a[cur]);
		}
		cur++;
	}
	Swap(&a[prev], &a[keyi]);
	keyi = prev;
	
	QuickSort(a, left, keyi - 1);
	QuickSort(a, keyi + 1, right);
}

快速排序(非递归)

逻辑步骤:
快速排序非递归的逻辑步骤

实现:
注意:这里需要用到栈。栈的相关知识链接:栈的实现

int PointerQuickSort(int* a, int left, int right)
{
	//三数取中法
	int midi = GetMidNum(a, left, right);
	if (midi != left)
		Swap(&a[midi], &a[left]);

	int keyi = left;
	int prev = left;
	int cur = left + 1;
	while (cur <= right)
	{
		if (a[cur] < a[keyi] && ++prev != cur)
		{
			Swap(&a[prev], &a[cur]);
		}
		cur++;
	}
	Swap(&a[prev], &a[keyi]);
	keyi = prev;
	return keyi;
}


void QuickSortNonR(int* a, int left, int right)
{
	Stack st;
	StackInit(&st);
	StackPush(&st, left);
	StackPush(&st, right);

	while (!StackEmpty(&st))
	{
		int end = StackTop(&st);
		StackPop(&st);
		int begin = StackTop(&st);
		StackPop(&st);

		int keyi = PointerQuickSort(a, begin, end);

		//两个子序列的判断 + 压栈
		if (keyi + 1 < end)
		{
			StackPush(&st, keyi + 1);
			StackPush(&st, end);
		}
		if (keyi - 1 > begin)
		{
			StackPush(&st, begin);
			StackPush(&st, keyi - 1);
		}
	}

	StackDestroy(&st);
}

归并排序

基本思想: 采用的分治思想,将已经有序的子序列合并,得到完全有序的序列。将两个有序的序列合成一个有序的–>二路归并。

归并排序动图:
归并排序动图

逻辑步骤:
归并排序逻辑步骤

归并排序递归实现

void _MergeSort(int* a, int left, int right, int* tmp)
{
	if (left >= right)
		return;
		
	int mid = (left + right) / 2;
	_MergeSort(a, left, mid, tmp);
	_MergeSort(a, mid + 1, right, tmp);

	int begin1 = left, end1 = mid;
	int begin2 = mid + 1, end2 = right;
	int i = left;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] > a[begin2])
		{
			tmp[i++] = a[begin2++];
		}
		else
		{
			tmp[i++] = a[begin1++];
		}
	}

	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}

	while (begin2 <= end2)
	{
		tmp[i++] = a[begin2++];
	}

	//注意拷贝的位置,左右之间的关系
	memcpy(a + left, tmp + left, sizeof(int) * (right - left + 1));
}


void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (NULL == tmp)
	{
		perror("malloc::fail");
		return;
	}
	
	_MergeSort(a, 0, n - 1, tmp);
	
	free(tmp);
	tmp = NULL;
}

归并排序非递归实现

逻辑实现步骤:
归并排序非递归实现逻辑步骤

void MergeSortNonR2(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (NULL == tmp)
	{
		perror("malloc::fail");
		return;
	}

	int gap = 1;
	while (gap < n)
	{
		for (int i = 0; i < n; i += 2 * gap)
		{
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
			//printf("front:[%d,%d][%d,%d]\n", begin1, end1, begin2, end2);

			if (end1 >= n || begin2 >= n)  //不用归并的直接结束即可
			{
				break;
			}
			else if (end2 >= n)  //修正 + 归并
			{
				end2 = n - 1;
			}
			//printf("back:[%d,%d][%d,%d] ", begin1, end1, begin2, end2);

			int j = i;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] > a[begin2])
				{
					tmp[j++] = a[begin2++];
				}
				else   
				{
					tmp[j++] = a[begin1++];
				}
			}
			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1++];

			}
			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2++];
			}

			memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
		}

		gap *= 2;
	}

	free(tmp);
}

非比较排序

计数排序

基本思想:

  1. 统计相同元素出现次数
  2. 根据统计的结果将序列回收到原来的序列中

逻辑步骤:
计数排序逻辑步骤
实现:

void CountSort(int* a, int n)
{
	int min = a[0];
	int max = a[0];
	for (int i = 1; i < n; i++)
	{
		if (min > a[i])
		{
			min = a[i];
		}
		if (max < a[i])
		{
			max = a[i];
		}
	}

	//寻找数据的范围
	int range = max - min + 1;
	//开辟一个数组计数
	int* count = (int*)calloc(range, sizeof(int));
	if (NULL == count)
	{
		perror("calloc::fail");
		return;
	}

	//计数
	for (int i = 0; i < n; i++)
	{
		count[a[i] - min]++;
	}

	//排序
	int j = 0;
	for (int i = 0; i < range; i++)
	{
		while (count[i]--)
			a[j++] = i + min;
	}

	free(count);
	count = NULL;
}

总结

八大排序的算法复杂度和稳定性分析

稳定性的概念: 在一串数值中会有相同的值,在完成排序后,相同的值之间的相对位置未发生改变

时间复杂度空间复杂度稳定性
直接插入排序O(N^2)O(1)稳定
希尔排序O(N^1.3)约O(N*logN)O(1)不稳定
简单选择排序O(N^2)O(1)不稳定
堆排序O(N*logN)O(1)不稳定
冒泡排序O(N^2)O(1)稳定
快速排序O(N*logN)O(logN) ~ O(N)稳定
归并排序O(N*logN)O(N)稳定

八大排序的性能的比较

注意: 当输出为0的情况是该排序的性能不满足数据量较大的时候,所以去除了该排序的比较

  1. 当数据量为10000时
    排序测试
  2. 当数据量为50000时
    排序测试
  3. 当数据量为5000000时
    排序测试
  4. 当数据量为10000000时
    排序测试

小结

八大排序各有特色。
有些适用于教学,有些适用于数据量较大,有些适用于数据量不大不小的时候,还有些适用于数据量较大时选择前十个或者后十个。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kpl_20

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值