数据结构 —— 5 排序

1 简单排序(冒泡、插入)

排序的数目在1万个以上的话,效率很重要

1.1 前提

在这里插入图片描述
待排元素存在数组里(A[]),N 表示要排序的数的个数
内部排序:假设内存空间足够大,所有数都被导到内存里,所有数的排序都在内存中完成、

1.2 冒泡排序

在这里插入图片描述
目的是把最小的泡泡排到最上面,最大的排在最下面
从上到下比较2个泡泡(第0个和第1个),如果小的在上面,大的在下面,则不动,否则,把这2个泡泡交换
然后在往下走比较2个相邻的(第1个和第2个)
依次排到最后,完成第一次排序,如下图
在这里插入图片描述
而且,经过第一趟排序,最大的肯定在最下面
第二趟,对前面的n-1个重复
在这里插入图片描述
但是,如果运气比较好,在上面某一趟排序顺序就排好了的话,即某一趟没有1次交换次序(执行swap函数)
在这里插入图片描述
最好情况:扫描一次就可以了
最坏情况:要排成从小到大的,但给的是从大到小的
好处:
1、 简单
2、同样也适用于链表(其他排序可能做不到)
3、严格大于时才做交换,保证了排序算法稳定
坏处:
最坏情况下,时间复杂度O(N2)不可接收
在这里插入图片描述

1.3 插入排序

打牌
假设最开始时第0张牌已经在手里了
从后向前比,找到位置后就把这个位置及以后的向后挪一个
在这里插入图片描述
好处:
1、简单
2、相比冒泡两两比较用3步来说,插入排序步骤只有1步,较简单
3、稳定
4、
坏处
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.4 时间复杂度下界

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
冒泡和插入交换的次序都是9次,每次交换时都是消掉一个逆序对、
在这里插入图片描述
序列基本有序,即I很小,排序算法会很快
在这里插入图片描述
欧米噶:下界

2 希尔排序

利用了插入排序的简单,但克服了每次只消除1个逆序对的缺点
在这里插入图片描述
进行a-间隔排序(先将相隔a个的几个数进行排序)
进行b-间隔排序
进行c-间隔排序
。。。
进行1-间隔排序
a>b>c>.。。。>1
一个很重要的性质:后一个间隔排序后仍保持前一个间隔排序后的性质
在这里插入图片描述
原插入排序是第0张牌已经在手里了,间隔1张牌排序
性质是第D张牌在手里了,间隔D张牌排序
在这里插入图片描述
既是上界又是下界
在这里插入图片描述
在这里插入图片描述
对于上万的数,希尔+Sedgewick复杂度比较低
在这里插入图片描述
不稳定,因为记录跳跃式地移动导致排序方法是不稳定的

3 堆排序

3.1 选择排序

依次从头找到最小元,把它放到前面,然后再从这个位置后再寻找最小元,放到刚才那个位置后面
在这里插入图片描述
Swap(A[i],A[MinP]):交换的数可能不是挨着的,所有可能就会消掉很多逆序对
最坏情况下,是每次都要换一次,所以Swap的时间复杂度是线性的(O(N))
找最小元ScanForMin()也是一个for循环
因此无论最好情况,最坏情况,都是O(N2)
如何快速找到最小元?可以用堆排序

3.2 堆排序

对选择排序的改进

算法1

比较笨的方法,建好堆后,把堆依次输出
在这里插入图片描述

算法2

最开始的数组
在这里插入图片描述
算法2不是把它调成最小堆,而是调成最大堆
每个子树依次调整,调完后:
在这里插入图片描述
因为是从小到大排序,所以要把最大的d与最后一个节点交换,即:
在这里插入图片描述
放到下面后,就把堆的整个规模减1(把d排除在外),因为d已经放到了最终的位置
然后把除d以外的堆调成最大堆
在这里插入图片描述
把c与堆里的最后一个节点(即a)交换,把c砍掉
在这里插入图片描述
然后再把a和b调整成最大堆,
在这里插入图片描述
然后再做交换
在这里插入图片描述
最后就完成了堆排序
在学堆时,a[0]的位置是不放元素的,放的是哨兵,但在排序时,a[0]放的是元素
在这里插入图片描述
在这里插入图片描述
PercDown(A,i,N):向下过滤子函数,i是根节点所对应的位置
在这里插入图片描述
2题应该是算法2,先调成最大堆
在这里插入图片描述
在这里插入图片描述

4 归并排序

4.1 有序子列的归并

在这里插入图片描述
这里的指针(Aptr\Bptr\Cptr)在数组里指的是位置
比较A指向的和B指向的数的值:
在这里插入图片描述
在这里插入图片描述
每个元素被扫描一遍,时间复杂度显然就是O(N)
在这里插入图片描述
A[]:包含2个数组,左边为第一个,右边为第2个,2个数组挨着存
最后一步还要把排好序的数组TmpA[]存回A[],但是L已经不是在指A[]的开始了,但是RightEnd是没动的,所以从后向前存

4.2 递归算法

在这里插入图片描述
时间复杂度没有最好没有最坏也没有平均
在这里插入图片描述
在Merge_sort()里声明TmpA是很有好处的,在Merge函数里才真正用到了TmpA
为什么有好处?
事先声明了的:
在这里插入图片描述
对上面第一段排序时,用到下面的绿色的那块进行Merge,然后再把绿色的那块地内容导回上面
在这里插入图片描述
会反复使用下面的Tmp数组的空间
但是如果没事先声明:
在这里插入图片描述
在这里插入图片描述
会进行大量的malloc和free
所以还是先开一个数组,然后每次传指针进去比较合算

4.3 非递归算法

递归要使用系统的堆栈,还有很多额外的操作,使得递归比较慢
在这里插入图片描述
相邻2个合成一个子序列,直到合成一个最大的序列
会使用很多的空间(logn层)
在这里插入图片描述
能够做到的最小的二额外空间复杂度为O(N)
只需要开一个临时数组:把A归并到临时数组里,下次再把临时数组归并到A。。。最后一步可能在A中,也可能在临时数组中
在这里插入图片描述
for循环归并到倒数第2对子序列,所以i<=N-2length
最后的一对可能数量跟之前的不太相同,所以要进行特殊处理
Merge()最终都要把Tmp数组导回A,但是这里不用,所以使用Merge1
Merge1(A,TmpA,i,i+length,i+2
length-1):

  • i:第1段的起始位置
  • i+length:第2段的起始位置
  • i+2*lenght-1:第2段的结束位置

在这里插入图片描述
归并排序最好最差时间复杂度都是O(nlogn)
但是需要额外的空间
所有数据都在内存时不用归并
外部排序时归并很有用

5 快速排序

5.1 算法概述

最快的算法,但还是会有最坏情况,自己写的话,如果某些细节实现的不好,就容易使算法不再是快速排序了
与归并都使用了分而治之
在这里插入图片描述
选一个主元,比他大的放右边,比他小的放左边,进行排序
在这里插入图片描述
递归什么时候结束?如果只有1个元素就证明结束了
问题:
1、如何选主元
2、如何划分子集
这2个问题解决不好,快排就不快了
在这里插入图片描述
在这里插入图片描述

5.2 选主元

在这里插入图片描述
在这里插入图片描述
使用的是最左边最右边和中间三个数的中位数,三个数交换后,最左边存的是三个数里的最小值,最右边存的是三个梳理的最大值,那么这时候,最左边和最右边的数已经分好了。然后把中间那个数(即a[center])放到a[right-1]处,这样就可以少考虑这3个数,只去处理a[left+1]-a[right-2]这几个数就行了

5.3 子集划分

以下为a[left+1]-a[right-2]
红色的6是选中的主元
在这里插入图片描述
目标是让左边的元素都<6,右边都>6
在这里插入图片描述
i指向的<6,则指针后移;j指向的>6,则指针前移
当发现i指向的>6,j指向的<6,,则将这2个指向的值互换
如下图,变成如下:
在这里插入图片描述
交换完后,i向前移,直到指向9,此时>6了
在这里插入图片描述
开始去移动j,向前移,直到指向j,此时<6了
在这里插入图片描述
交换这2个元素
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
快排为什么快?因为它把主元一次性放到了最终的位置,以后不会被移动了
不像插入排序,放的位置只是临时的
如果有元素正好等于pivot怎么办?

  • 停下来交换?
  • 不理它,继续移动指针?

考虑极端情况,数组中的值都相等

  • 如果停下里交换,这些数会做很多无意义的交换,一个好处就是每次都会分成2个序列,递归下去,复杂度为nlogn
  • 如果不理他,i会一直移动到最右端,j没有机会移动,所以主元会被放在端点,所以这是那个很囧的情况,时间复杂度会变为N2
  • 所以还是选择停下里交换比较好

在这里插入图片描述

5.4 算法实现

在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
如果某元素正好等于主元,则会交换,这样就会导致是不稳定的

6 表排序

6.1 算法概述

如果要排的不是一个元素,而是一个结构体,则移动这个结构体的时间不可忽略不计
上面的算法都要交换
使用表排序,移动的是指针
在这里插入图片描述
比较key,不交换整体,而只是交换table的指针
使用插入排序后,table的值变为:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6.2 物理排序

必须要把哪些结构都排序出来
在这里插入图片描述
根据变化后得table值,确定出几个环
如:table[0]=3,则把table[3]的值归到环中,table[3]=1,则把table[1]的值归到环中。。。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
移动2个数需要3步,所以会比较耗时

7 基数排序

7.1 桶排序

在这里插入图片描述
在这里插入图片描述
把学生成绩插入需要N,输出时扫描全部桶需要M
在这里插入图片描述

7.2 基数排序

在这里插入图片描述
在这里插入图片描述
一共执行P趟,每一趟分配N个元素,并且访问B个桶

7.3 多关键字排序

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
就扑克牌而言,次位优先比主位优先聪明
在这里插入图片描述
在这里插入图片描述

8 排序算法比较

在这里插入图片描述
选择排序都是挑着排的,所以不稳定
冒泡和直接插入都是相邻2个交换,所以稳定,它们不需要额外空间
在这里插入图片描述
在这里插入图片描述
2. 竟然直接把插入排除了,如果是逆序的话,插入排序,在最后一趟前,所以元素肯定都不在最终位置,因为最后插入的数最小,要把它挪到第一个,其他的数都要向后挪一个
3. 调整成最小堆,基本有序时效率也不会很差
4. 冒泡:2趟排序后不可能会出现3,2
插入:插入2后就会变为2,3
选择:第一个数肯定是最小值
在这里插入图片描述
选择排序和堆排序可以不用全部排完就可以得到排序后的前10个数
但是选择排序,需要遍历所有的数找到最大的值,这个比较耗时
(还有代码们没有写一写)

猜你喜欢

转载自blog.csdn.net/qq_42713936/article/details/106045220