C语言实现八大排序(动画演示)

前言

排序算法有很多,因其思想及依赖的数据结构不同造成对应排序方法性能的不同。本文将介绍常用的八大排序算法。让大家对各类排序有所了解。

八大排序

注意:本文介绍的排序均为排升序

排序的概念

排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
内部排序:数据元素全部放在内存中的排序。
外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。

  • 稳定性介绍:
    稳定性介绍

特性总结

为方便大家更好理解下面实现的各类排序,先对各类排序算法特性进行总结

排序方式 平均情况 最好情况 最坏情况 空间复杂度 稳定性
直接插入排序 O(N^2) O(N) O(N^2) O(1) 稳定
选择排序 O(N^2) O(N^2) O(N^2) O(1) 不稳定
冒泡排序 O(N^2) O(N^2) O(N^2) O(1) 稳定
希尔排序 O(N^1.3) O(N^1.25) O(N^1.6) O(1) 不稳定
堆排序 O(N*logN) O(N*logN) O(N*logN) O(1) 不稳定
快速排序 O(N*logN) O(N*logN) O(N^2) O(logN~N) 不稳定
归并排序 O(N*logN) O(N*logN) O(N*logN) O(N) 稳定
计数排序 O(max(range,N)) O(max(range,N)) O(max(range,N)) O(range) 稳定
  • 不稳定的原因在下图:
    稳定性解释

冒泡排序

开始先上盘开胃小菜:想必冒泡排序大家都不陌生了

思想:

冒泡就是将当前位置的元素与后一个位置的元素进行比较,符合排序要求就交换两个位置的元素。

冒泡排序

void Swap(int* p1, int* p2)
{
   
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

void BubbleSort(int* a, int n)
{
   
	//每一趟
	for (int i = 0; i < n; i++)
	{
   
		int flag = 1;//优化
		//每一轮
		for (int j = 0; j < n - i - 1; j++)
		{
   
			if (a[j] > a[j + 1])
			{
   
				Swap(&a[j], &a[j + 1]);
				flag = 0;
			}
		}
		//如果一轮下来都不需要交换元素,则说明数组元素已经有序,则可直接跳出循环
		if (flag == 1)
		{
   
			break;
		}
	}
}

实现:

冒牌排序需要嵌套两层循环,外层循环控制趟数,内层循环控制排序元素的位置。每一趟排序完成会将当前轮的最值排到最后一个位置。需要注意的是内外循环结束的条件

  • 外层循环控制的是趟数:每一趟只将当前趟数的最值排到最后的位置,一趟只排好一个位置,也就是需要排n-1趟
  • 内层循环控制排序位置:每一趟会将元素排好在最后面,所以每一轮就不需要排到已经排好元素的位置,所以停止条件为j < n - i - 1

冒泡排序的时间复杂度是经典的O(N^2),这是因为嵌套两层循环,而且每轮只能排好一个元素,效率是非常低下的,即便我们增加了flag来优化:判断当前轮是否已经排完序,是则无需再排。但效率依旧很低。

选择排序

一般的选择排序一轮只选最大或者最小的元素,效率低下。我们这里直接升级一下,同时选最大和最小,一轮排一大一小两个元素。

思想:
每一次从待排序的数据元素中选出最小和最大的两个元素,存放在序列的起始,结束位置,直到全部待排序的数据元素排完 。(下图演示的是只选一个的选择排序)

//优化版,同时选最大的和最小的,将大的往后放,小的往前放,不断缩小区间
void SelectSort(int* a, int n)
{
   
	int begin = 0;
	int end = n - 1;
	while (begin < end)//相遇则说明已经排好
	{
   
	    //都从开始位置选
		int maxi = begin;
		int mini = begin;
		for (int i = begin; i <= end; i++)//遍历选出当前区间的最值
		{
   
			if (a[i] < a[mini])
			{
   
				mini = i;
			}

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

		Swap(&a[begin], &a[mini]);//一轮选出了两个最值,交换
		if (maxi == begin)//要注意前一轮交换是否会影响下一轮
		{
   
			maxi = mini;//若最大值在第一个位置,则需要更新一下交换后最大值的下标位置
		}
		Swap(&a[end], &a[maxi]);
		begin++;//控制区间
		end--;
	}
}

选择排序

实现:
选择排序也由两层循环一起完成:外层用来检测元素是否相遇,相遇则说明已经排完序了。内层循环用来控制待排序区间:先选出最小和最大值,再将其与当前区间首尾位置元素进行交换。最后不断缩小当前区间,直到排好序。

  • 需要注意的是在每轮第一次交换是否会影响到下一一次交换。

即便我们推出优化版本,但选择排序的思想注定了它的时间复杂度只能是

直接插入排序

思想:

我们将待排序元素与其前面位置的元素进行比较(前面元素已经有序),直到找到目标位置将其插入

实际中我们玩扑克牌时,就用了类似的思想
扑克牌插入排序
直接插入排序

//类似插排,将要插入的数与前面的数(已有序)一一比较,将元素后移,直到找到要插入的位置
void InsertSort(int* a, int n)
{
   
	for (int i = 1; i < n; i++)
	{
   
		int tmp = a[i];//要插入的值
		int prev = i - 1;//插入值的前一位
		while (prev >= 0)
		{
   
			if (a[prev] > tmp)
			{
   
				a[prev+1] = a[prev];//将元素后移
				prev--;//向前继续比对
			}
			else//找到插入位置
			{
   
				break;
			}

		}
		a[prev + 1] = tmp;//插入,prev位置之后
	}
}

实现:
由于待排序元素前面已经是有序数组,所以我们从第二个位置开始排序。先用临时变量tmp记录待排序元素,然后将其与前面位置的元素一一比较,如果前一个大于tmp(此时排升序),则将前面的元素往后覆盖,直至找到位置break出循环。这时候再将tmp插入目标位置(目标位置为前一元素之后)

  • 需要注意的是排序位置之前已经是有序数组,要记住这一点,这也是在循环里为什么不是往后覆盖数据就是break出循环的原因。
  • 还有一点就是待插入元素的位置:以升序为例,如果前一元素已经小于待插入元素,则主动跳出循环,再将其插到前一元素的后面,也就是prev + 1的位置。如果待插入元素是最小值,那么只能等prev越界,此时prev + 1便为首元素位置。

直接插入排序的时间复杂度也为O(N^2) ,但与前面两个O(N^2) 的相比,直接插入排序是O(N^2)家族里的唯一牌面了,虽说是同一级别的,但在实际中也还能拿的出手
直接插入排序
以上单位为毫秒,对100000个数据进行排序(测试方法后续再讲解)。
该测试可以看出冒泡是真的没得救了,完全上不了桌,选择排序优化后好像也还能用,插入排序与他们相比给人一种错觉:好像他们不是这个量级的。

  • 他们的时间复杂度都为O(N^2) 没错,这是由于时间复杂度表示方法的原因,将他们都归为了一类,但在实际中直接插入排序最好情况下是可以达到O(N)的
  • 结合动图及代码可以看到,当直接插入排序要排的是接近有序数组时,一趟下来就能将数组排好,做到O(N)的级别

希尔排序

我们的大佬Shell,对上面的直接插入排序很看好,能够有O(N)级别的潜力,觉得很有潜力,是个潜力股,决心好好挖掘一下。

思想:

希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数gap作为距离,把待排序数据中所有个数记录分成n个组,所有距离为gap的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达gap=1时,所有记录在统一组内排好序。

希尔排序图示
希尔排序

//1 当gap不为1时为预排序
//2 当gap为1时是插入排序
void ShellSort(int* a, int n
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值