常用经典排序算法详解分析与总结

本文详细介绍了包括冒泡排序、选择排序、插入排序等在内的多种经典排序算法,对比了它们的时间和空间复杂度,并提供了每种算法的代码实现。

常用经典排序算法详解分析与总结

推荐:

https://www.cnblogs.com/onepixel/articles/7674659.html
https://blog.youkuaiyun.com/daguairen/article/details/52611874

0:、说明:

0.1:虽然大家对排序算法可能熟悉,但是有好多人不明白描述算法的专业术语,下来我先解释一下术语描述:
(1) 稳定:如果在数组中,两个相等的元素(即 a=b)a原来的位置在元素b的前面,排序结束之后a的位置任然在b的前面,那么说这个排序算法是稳定的;
(2)不稳定:如果在数组中,两个相等的元素(即 a=b)a原来的位置在元素b的前面,排序结束之后a的位置可能出现在了b的后面,那么说这个排序算法是不稳定的;
**(3)**内排序:所有排序操作都在内存中完成;
**(4)**外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行;
**(5)**时间复杂度:一个算法执行所耗费的时间;
**(6)**空间复杂度:运行完一个程序所需内存的大小;
0.2:常用排序算法复杂度对照表
这里写图片描述
这里写图片描述
0.3:排序算法分类:
(1)插入排序:直接插入排序, 希尔排序
(2)选择排序:简单选择排序, 堆排序
(3)交换排序:冒泡排序, 快速排序
(4)归并排序
(5)基数排序
(6)计数排序
(7)桶排序
在这里插入图片描述
0.4总结:
(1)稳定的算法(平均时间下):
* 插入排序O(n²)
* 冒泡排序O(n²)
* 归并排序O(n log n); (需要 O(n) 额外存储空间)
* 二叉树排序O(n log n); (需要 O(n) 额外存储空间)
* 计数排序O(n + k); (需要 O(n + k) 额外存储空间, k为序列中Max-Min+1)
* 桶排序O(n); (需要 O(k) 额外存储空间, k为桶的个数)
(2)不稳定的算法:
* 选择排序O(n²)
* 快速排序O(n log n)
* 堆排序O(n log n)
* 希尔排序O(n log n)
* 基数排序O(n * k); (需要 O(n) 额外存储空间 ,K为特征个数)

1. 冒泡排序

1.1算法描述
(1)比较相邻元素,如果第一个比第二个大,就交换;
(2)对每一对元素比较,这样完成了一组
(3)重复上述步骤,完场剩下的n-1组
1.2动图演示
![在这里插入图片描述](https://img-blog.youkuaiyun.com/20181009125308193?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2ZlaXlhbmFmZmVjdGlvbg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
1.3 代码实现
void BubbleSort(int s[], int n)
{
	int i,j,exchange = 1;
	for(i=0; i<n-1 && exchange; i++)
	{
		exchange = 0;
		for(j=0; j<n-i-1; j++)
			if(s[j] > s[j+1])
			{
				std::swap(s[j], s[j+1]);
				exchange = 1;
			}
	}
 }
1.4 算法分析
最好情况:O(n)
平均情况:O(n²)
最差情况:O(n²)

2. 直接选择排序:

2.1 算法描述

(1)第一趟在(0,1,2…n-1)中选择一个最小, 一个最大的,然后交换;
(2)第二趟在(1,2,…n-1)中选择一个最小, 一个最大的,然后交换;
(3)重复上述步骤,直到n-1趟

2.2 动图演示

这里写图片描述

2.3 代码实现
void SelectSort(int s[], int n)
{
	int i, j;
	int temp;
	for(i=0; i<n-1; i++)
	{
		for(j=i+1; j<n; j++)
			if(s[i] > s[j])
			{
				std::swap(s[i], s[j]);
			}
	}
 } 
2.4 算法分析
最好情况:O(n²)
平均情况:O(n²)
最差情况:O(n²)

3. 直接插入排序

3.1 算法描述

(1)一般从数组第一个元素a[0]开始,可认为这一个元素已经是有序 的;
(2)分别用a[0]与每一个元素比较,直到比较到大于它的第一个元素,然后将之前的元素向前移动,在当前位置插入a[0]元素;
(3)然后重复步骤从a[1]开始比较,直到n-1个元素;

3.2 动图演示

这里写图片描述

3.3 代码实现
void InsertSort(int s[], int n)
{
	int i, j;
	for(i=2; i<n; i++)
	{
		// 一般把a[0]当做位置移动的缓冲空间
		s[0] = s[i];   //给岗哨赋值 
		j = i - 1;     //确定要比较的最右边的元素 
		while(s[0] < s[j])
		{
			s[j+1] = s[j];	//数据右移 
			j--;		//向左移动找到要比较的下一个元素 
		}
		s[j+1] = s[0];   // 在当前位置插入
	}
 } 
3.4 算法分析
最好情况:O(n)
平均情况:O(n²)
最差情况:O(n²)

4. 希尔排序

4.1 算法描述

(1)在直接插入排序的基础上,每次移动的距离变成了d
(2)实现方法同直接插入排序

4.2 动图演示

这里写图片描述

4.3 代码描述
void ShellSort(int s[], int n)
{
	int i, j, d;
	d = n / 2;  //设置固定增量值 
	while(d > 0)
	{
		for(i=d+1; i<n; i++)
		{
			s[0] = s[i];  //给岗哨赋值 
			j = i - d;  //确定要比较的最左边的一组元素 
			while(j > 0 && s[0] < s[j])
			{
				s[j+d] = s[j];	 //数据右移 
				j -= d;	  //寻找下一个要比较的元素 
			}
			s[j+d] = s[0];	//在确定位置插入s[i] 
		}
		d /= 2; 
	}
}
4.4 算法分析
最好情况:O(n log2 n)
平均情况:O(n log n)
最差情况:O(n log2 n)

5. 归并排序

5. 1 算法描述
(1)把长度为n的输入序列分成两个长度为n/2的子序列;
(2)对这两个子序列分别采用归并排序;
(3)将两个排序好的子序列合并成一个最终的排序序列
5.2 动图演示

这里写图片描述

5.3 代码实现
void Merge(int r[], int s[], int x1, int x2, int x3)	//实现一次归并排序的函数 
{
	int i, j, k;
	i = x1;		//第一部分的开始位置 
	j = x2 + 1;	//第二部分的开始位置 
	k = x1;
	while(i <= x2 && j <= x3)	//当i和j都在两个要合并的部分中时 
		if(r[i] <= r[j])	//筛选两部分中较小的元素放到数组s中 
			s[k++] = r[i++];
		else
			s[k++] = r[j++];
		
	
	while(i <= x2)		//将x1~x2范围内未比较的数顺次加到数组r中 
		s[k++] = r[i++];
		
	while(j <= x3)		//将x2+1~x3范围内未比较的数顺次加到数组r中 
		s[k++] = r[j++];
	
}

void MergeSort(int r[], int s[], int start, int end) 
{
	int mid;
	int t[20];
	if(start == end)
		s[start] = r[start];
	else
	{
		mid = (start + end) / 2;
		
		//递归调用将r[start]~r[mid]归并成有序的t[start]~t[mid]
		MergeSort(r, t, start, mid);
		
		//递归调用将r[mid+1]~r[end]归并成有序的t[mid+1]~t[end]
		MergeSort(r, t, mid+1, end);
		
		//调用函数将前面两部分归并到s[start]~s[end] 
		Merge(t, s, start, mid, end);
	}
}
5.4 算法分析
最好情况:O(n)
平均情况:O(n log n)
最差情况:O(n log n)

6. 快速排序

6.1 算法描述

快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
(1)从数列中挑出一个元素,称为 “基准”;
(2)重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置;
(3)递归地把小于基准值元素的子数列和大于基准值元素的子数列排序。

6.2 动图演示

这里写图片描述

6.3 代码实现
void QuickSort(int s[], int start, int end)
{
	int i, j;
	i = start;		//将每组首个元素赋给i 
	j = end;		//将每组末尾元素赋给j 
	s[0] = s[start];	//设置基准值 
	while(i < j)
	{
		while(i < j && s[0] < s[j])
			j--;
		if(i < j)	//找到了比基准值s[0]小的元素放到最左边 
		{
			s[i] = s[j];
			i++; 	//向右移动一位 
		} 
		while(i < j && s[0] >= s[i])
			i++;
		if(i < j)	//找到了比基准值s[0]大的元素放到最最右边 
		{
			s[j] = s[i];
			j--;	//向左移动 
		}
	}
	s[i] = s[0];
	
	//将分好的三组值再次调用递归细分组,最终完成排序 
	if(start < i)
		QuickSort(s, start, j-1);
	if(i < end)
		QuickSort(s, j+1, end);
}
6.4 算法分析
最好情况:O(n log n)
平均情况:O(n log n)
最差情况:O(n²)

7. 堆排序

7.1 算法描述

堆排序是利用数据结构二叉树的结构,并同时满足堆积的性质,按照大根堆(根节点大于左右孩子结点)或小根堆(根节点小于左右孩子结点)结点的索引进行结点交换

7.2 动图演示

这里写图片描述

7.3 代码实现
/*a时数组元素,i是待调整的数组元素位置,n是数组长度*/
void Sift(int a[], int i, int n)
{
	int Lnode, temp;		
	//Lnode时左子节点,Lnode+1时右子节点 
	for(temp=a[i]; 2*i+1<n; i=Lnode)
	{
		//左孩子节点位置是2*i, 右孩子节点的位置是2*i+1 
		Lnode = 2*i+1;
		//得到结点中较大的结点 
		if(Lnode != n - 1 && a[Lnode + 1] > a[Lnode])
			++Lnode;
		if(temp < a[Lnode])
			a[i] = a[Lnode];
		else
			break;
	}
	a[i] = temp;
}
void HeapSort(int a[], int n)
{
	int i;
	
	/*调整序列的前半部分元素,(即每个有孩子的结点),调整完之后是一个大根堆*/
	for(i=n/2 - 1; i>=0; i--)
		Sift(a, i, n);
	for(i=n - 1; i>0; i--)
	{
		std::swap(a[i], a[0]);
		Sift(a, 0, i);
	}
 } 
7.4 算法分析
最好情况:O(n log n)
平均情况:O(n log n)
最差情况:O(n log n)

基数排序、计数排序、桶排序原理及代码实现看:

参考:

https://www.cnblogs.com/onepixel/articles/7674659.html

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

phial03

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

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

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

打赏作者

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

抵扣说明:

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

余额充值