数据结构之经典排序算法

常用排序算法

冒泡排序

(升序)从前往后两两比较,若后者比前者小,则交换,继续向后对比,每一趟排序挑选出至少一个元素在其正确位置上.
冒泡排序最好的时间复杂度为 O ( n ) O(n) O(n),最坏情况为 O ( n 2 ) O(n^2) O(n2),平均时间复杂度为 O ( n 2 ) O(n^2) O(n2).稳定排序

鸡尾酒排序

鸡尾酒排序的原理跟冒泡排序差不多,只不过冒泡排序每一轮的比较都是从左至右依次比较,而鸡尾酒排序则是一轮从左至右比较,下一轮从右至左比较。

插入排序

插入排序是在前面已排序数组找到插入的位置。
插入排序为稳定排序,内排序,当基本有序时,为最好时间复杂度为 O ( n ) O(n) O(n),最差与平均时间复杂度为 O ( n 2 ) O(n^2) O(n2).

图书馆排序

插入排序的改进版本,它在元素之间添加了一些空位置,以便在插入排序过程中向后依次移动元素的时候可以减少移动的次数。
具体算法描述如下:假设我们有一个n元数组,然后我们选定了元素间需要预留的空当大小,这样最后这个数组大小是(1 + ε)n。我们需要使用二分查找方式找到元素需要插入的位置,接着往数组里插入元素时,因为元素之间有空当,我们需要移动的元素数量会少过普通插入排序,在插入步骤完成后,我们需要执行重平衡(re-balancing,即给元素之间再补充上需要的空当)

图书馆排序的最坏时间复杂度是 O ( n 2 ) O(n^2) O(n2),即要插入的地方都在同一处,留空位的策略根本没用;最好的时间复杂度是线性的 O ( n ) O(n) O(n);平均时间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn)
它的缺点在于额外的空间占用,还有一个缺点来自于插入排序,存在大量的交换操作,如果这样的交换导致的写操作开销大的话会成为一个问题(虽然在插入步骤中开销已经好过普通的插入排序,但是在rebalancing步骤中增加了额外的开销)。

各步骤消耗时间如下:

  • 二分查找步骤,线性时间;
  • 插入步骤,插入元素,如果没有插入空当,需要向后移动元素,直到空当出现;
  • 重平衡,给元素之间插入需要的空当,这也应该是线性时间的,因为总共有O(logn)轮,所以总的时间复杂度是O(n*logn)。

计数排序

计数排序是典型的空间换时间算法,开辟额外数据空间存储用索引号记录数组的值和数组值个数。具体算法描述如下:

1)找出待排序的数组的最大值和最小值
2)统计数组值的个数
3)反向填充目标数组

计数排序为稳定排序,外排序,时间复杂度O(n + k),但是对于数据范围很大的数组,需要大量时间和内存。

桶排序

桶排序是对计数排序的改进,计数排序申请的额外空间跨度从最小元素值到最大元素值,若待排序集合中元素不是依次递增的,则必然有空间浪费情况。桶排序则是弱化了这种浪费情况,将最小值到最大值之间的每一个位置申请空间,更新为最小值到最大值之间每一个固定区域申请空间,尽量减少了元素值大小不连续情况下的空间浪费情况。
桶排序是将待排序集合中处于同一个值域的元素存入同一个桶中,也就是根据元素值特性将集合拆分为多个区域,则拆分后形成的多个桶,从值域上看是处于有序状态的。对每个桶中元素进行排序,则所有桶中元素构成的集合是已排序的。具体算法描述如下:
1)根据待排序集合中最大元素和最小元素的差值范围和映射规则,确定申请的桶个数;
2)遍历待排序集合,将每一个元素移动到对应的桶中;
3)对每一个桶中元素进行排序,并移动到已排序集合中。

桶排序的关键环节

元素值域的划分,也就是元素到桶的映射规则。映射规则需要根据待排序集合的元素分布特性进行选择,若规则设计的过于模糊、宽泛,则可能导致待排序集合中所有元素全部映射到一个桶上,则桶排序向比较性质排序算法演变。若映射规则设计的过于具体、严苛,则可能导致待排序集合中每一个元素值映射到一个桶上,则桶排序向计数排序方式演化。

排序算法的选择,从待排序集合中元素映射到各个桶上的过程,并不存在元素的比较和交换操作,在对各个桶中元素进行排序时,可以自主选择合适的排序算法,桶排序算法的复杂度和稳定性,都根据选择的排序算法不同而不同。

桶排序为稳定排序,外排序,时间复杂度O(n + k),k为桶的个数。

希尔排序

基于增量序列进行排序,我们选择增量gap=length/2,缩小增量继续以gap = gap/2的方式,这种增量选择我们可以用一个序列来表示,{n/2,(n/2)/2…1},称为增量序列。希尔排序的增量序列的选择与证明是个数学难题,我们选择的这个增量序列是比较常用的,也是希尔建议的增量,称为希尔增量,但其实这个增量序列不是最优的。先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,然后调整增量,继续对子序列进行插入排序,直到增量为1。具体算法描述如下:

1)选择一个增量序列 t 1 , t 2 , ⋅ , t k t_1,t_2,\cdot,t_k t1,t2,,tk,其中 t i > t j , i < j , t k = 1 t_i>t_j,i<j,t_k=1 ti>tj,i<j,tk=1

2):按增量序列个数k,对序列进行k 趟排序;

3):每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子序列进行直接插入排序。增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

希尔排序为不稳定排序,内排序;希尔排序的时间复杂度和增量序列是相关的,希尔排序在效率上优于直接插入排序,时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn).

归并排序

归并排序,采用是分治法,先将数组分成子序列,让子序列有序,再将子序列合并成有序数组。具体算法描述如下:

1)把长度为n的输入序列分成长度 n/2的子序列;
2)对两个子序列采用归并排序(递归);
3)合并所有子序列。

归并排序为稳定排序,外排序(占用额外内存),最差、最好、平均时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn).

从单个记录进行两两归并的排序算法一般不提倡,可将归并排序与插入排序结合使用,使用插入排序得到较长的有序子序列之后,可使用归并排序进行归并,因为插入排序是有序的,所以改进后的归并排序也是有序的。

基数排序

基数排序是对数字每一位进行排序,从最低位开始排序。
具体算法描述如下:

1)找到数组最大值,得最大位数;
2)从最低位开始取每个位组成radix数组;
3)对radix进行计数排序(计数排序适用于小范围的特点)。

基数排序稳定排序,外排序,时间复杂度 posCount * (n + n),其中 posCount 为数组中最大元素的最高位数;简化下得 O ( k ∗ n ) O(k*n) O(kn);其中k为常数,n为元素个数。

二叉树排序

构建二叉搜索树,进行中序遍历即可得到排序数组。

选择排序

首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
稳定性:用数组实现的选择排序是不稳定的,用链表实现的选择排序是稳定的,内排序。

堆排序

堆排序是利用堆这个数据结构设计的排序算法。具体算法描述如下:

1)建堆,从底向上调整堆,使得父亲节点比孩子节点值大,构成大顶堆;
2)交换堆顶和最后一个元素,重新调整堆。

堆排序为不稳定排序,内排序,最差、最好、平均时间复杂度均为为 O ( n l o g n ) O(nlogn) O(nlogn)

快速排序

核心思想即分治法,首先在数组中选择一个基准点(该基准点的选取可能影响快速排序的效率),然后分别从数组的两端扫描数组,设两个指示标志(low指向起始位置,high指向末尾),首先从后半部分开始,如果发现有元素比该基准点的值小,就交换low和high位置的值,然后从前半部分开始扫描,发现有元素大于基准点的值,就交换low和high位置的值,如此往复循环,直到low>=high,然后把基准点的值放到high这个位置。一次排序就完成了。以后采用递归的方式分别对前半部分和后半部分排序,当前半部分和后半部分均有序时该数组就自然有序了。每一趟排序挑选出至少一个元素在其正确位置上.

快速排序不稳定,平均复杂度为 O ( n l o g n ) O(nlogn) O(nlogn),最差情况为时间复杂度为 O ( n 2 ) O(n^2) O(n2)

排序算法的稳定性

在待排文件红,若存在多个关键字相同的记录,经过排序后这些具有相同关键字的记录之间的相对次序保持不变,则称该排序算法是稳定的,否则该排序算法不稳定。

稳定排序包括:冒泡排序,鸡尾酒排序,插入排序,归并排序,桶排序,基数排序,二叉树排序,图书馆排序

不稳定的排序包括:选择排序,希尔排序,堆排序,快速排序

排序算法的分类

按照是否涉及数据的内外存交换分

在排序过程中,若整个文件都是放在内存中处理(记录个数较少的小文件),排序时不涉及数据的内外存交换,则称为内部排序;反之,则称为外部排序(记录个数太多,不能一次读进内存的大文件)。

按照策略划分内部排序方法

5类:插入排序(希尔排序),选择排序,交换排序(冒泡,快排),归并排序,分配排序

排序算法的基本操作

两个基本操作:1)比较两个关键字的大小;2)改变指向记录的指针或者移动记录本身(依赖于待排序记录的存储方式)

待排文件的常用存储方式

1.顺序表作为存储结构

排序过程,通过关键字之间的比较判定,将记录移动到合适的位置;

2.链表作为存储结构

无需移动记录,仅需修改指针;

3.用顺序的方式存储待排序的记录,但同时建立一个辅助表(如包括关键字和指向记录位置的指针组成的索引表)

只需对辅助表进行物理重排,即只移动辅助表的表目,而不移动记录本身,适用于难在链表上实现,且仍需避免排序过程中移动记录的排序方法。

排序算法的性能评价

就地排序即空间复杂度为 O ( 1 ) O(1) O(1)的排序算法

排序算法平均时间复杂度最好情况最坏情况空间复杂度排序方式稳定性
冒泡排序 O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n) O ( n 2 ) O(n^2) O(n2) O ( 1 ) O(1) O(1)稳定
鸡尾酒排序 O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n) O ( n 2 ) O(n^2) O(n2) O ( 1 ) O(1) O(1)稳定
计数排序 O ( n + k ) O(n+k) O(n+k) O ( n + k ) O(n+k) O(n+k) O ( n + k ) O(n+k) O(n+k) O ( k ) O(k) O(k)稳定
桶排序 O ( n + k ) O(n+k) O(n+k) O ( n + k ) O(n+k) O(n+k) O ( n 2 ) O(n^2) O(n2) O ( n + k ) O(n+k) O(n+k)稳定
插入排序 O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n) O ( n 2 ) O(n^2) O(n2) O ( 1 ) O(1) O(1)稳定
图书馆排序 O ( n l o g n ) O(nlogn) O(nlogn) O ( n ) O(n) O(n) O ( n 2 ) O(n^2) O(n2) O ( 1 + ϵ ) n O(1+\epsilon)n O(1+ϵ)n
归并排序 O ( n l o g n ) O(nlogn) O(nlogn) O ( n l o g n ) O(nlogn) O(nlogn) O ( n l o g n ) O(nlogn) O(nlogn) O ( n ) O(n) O(n)稳定
二叉树排序 O ( n l o g n ) O(nlogn) O(nlogn) O ( n l o g n ) O(nlogn) O(nlogn) O ( n l o g n ) O(nlogn) O(nlogn) O ( n ) O(n) O(n)
基数排序 O ( d n ) O(dn) O(dn) O ( d n ) O(dn) O(dn) O ( d n ) O(dn) O(dn) O ( n ) O(n) O(n)稳定
希尔排序 O ( n l o g n ) O(nlogn) O(nlogn) O ( 1 ) O(1) O(1)不稳定
堆排序 O ( n l o g n ) O(nlogn) O(nlogn) O ( n l o g n ) O(nlogn) O(nlogn) O ( n l o g n ) O(nlogn) O(nlogn) O ( 1 ) O(1) O(1)不稳定
快速排序 O ( n l o g n ) O(nlogn) O(nlogn) O ( n l o g n ) O(nlogn) O(nlogn) O ( n 2 ) O(n^2) O(n2) O ( l o g n ) O(logn) O(logn)不稳定
选择排序 O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( 1 ) O(1) O(1)不稳定
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值