“排序”学习提纲
前言
“排序”学习提纲。
代码模板
排序的基本概念
相关概念
- 排序:关键字无序序列排列成有序序列的过程
关键字可以是主关键字、次关键字和关键字组合等
多关键字可以转换为单关键字
- 稳定性:排序前后,两个或两个以上相同的关键字,相对位置没有变化,称算法是稳定的,反之是不稳定的
若关键字无重复,则排序结果唯一
若关键字有重复,则排序结果不唯一,需要考虑算法的稳定性
判断方式:
若存在相邻地比较和移动/交换关键字,则是稳定的
若存在跳跃地比较和移动/交换关键字,则是不稳定的
类型(依据思想)
- 插入类排序
- 交换类排序
- 选择类排序
- 归并类排序
- 基数类排序
插入类排序:
- 直接插入排序
- 折半插入排序
- 希尔排序
交换类排序:
- 冒泡排序
- 快速排序
选择类排序:
- 简单选择排序
- 堆排序
归并类排序:
- 二路归并排序
基数类排序:
- 基数排序
类型(依据复杂度)
- 简单排序
- 复杂排序
简单排序:
- 直接插入排序
- 冒泡排序
- 简单选择排序
复杂排序:
- 希尔排序
- 快速排序
- 堆排序
- 归并排序
类型(依据数据是否完全在内存中)
- 内部排序(是)
- 外部排序(否)
内部排序:
- 插入类排序
- 交换类排序
- 选择类排序
- 归并类排序
- 基数类排序
基本操作
- 比较
- 移动
注意:基数类排序基于多关键字比较
时间复杂度->比较和移动的次数
插入类排序
直接插入排序
1 | //直接插入排序 |
折半插入排序
1 | //折半插入排序 |
希尔排序/缩小增量排序
1 | //希尔排序 |
交换类排序
冒泡排序/起泡排序 简单版
1 | //冒泡排序 简单版 |
冒泡排序 正版
1 | //冒泡排序 正版 |
冒泡排序 改进版
1 | //冒泡排序 改进版 |
快速排序
1 | //快速排序 |
堆
概念
- 数据结构中的完全二叉树。满足:任意一个非叶结点的关键字都不小于(或不大于)其左和右孩子结点的关键字
类型
- 大顶/根堆:任意一个非叶结点的关键字都不小于其左和右孩子结点的关键字
- 小顶/根堆:任意一个非叶结点的关键字都不大于其左和右孩子结点的关键字
插入过程
- 在最右下位置插入(满足完全二叉树定义)
- 调整(满足堆定义)
删除过程
- 交换最右下位置和删除位置的关键字(满足完全二叉树定义)
- 从最右下位置删除(满足完全二叉树定义)
- 调整(满足堆定义)
堆排序的过程
对大顶堆:
- 无序序列->完全二叉树(依据层序遍历方法)
- 完全二叉树->大顶堆(依据大顶堆调整方法)
- 大顶堆->有序序列(依据堆排序方法)
层序遍历方法:
- 从上到下,从左到右,构造完全二叉树
大顶堆调整方法:
- 从完全二叉树的最后一个非叶子结点开始,从右到左,从下到上,调整结点
- 交换结点以满足大顶堆定义
堆排序方法:
- 交换树根结点和最右下位置结点,删除最右下位置结点,输出最右下位置结点(最右下位置结点进入有序序列)
- 调整(最右下位置结点被交换到树根结点,可能不满足堆定义)
- 重复1和2步,堆无结点时结束
选择类排序
简单选择排序
1 | //简单选择排序 |
堆排序
1 | //堆调整 大顶堆法 |
归并类排序
二路归并排序 递归法 分治法
1 | //二路归并 |
基数类排序
基数排序
1 | //基数排序 |
总结
插入类排序
名称 | 时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|
直接插入排序 | O(n²) | O(1) | 稳定 |
折半插入排序 | O(n²) | O(1) | 稳定 |
希尔排序 | O(n的1.3次方)~O(n²) | O(1) | 不稳定 |
交换类排序
名称 | 时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|
冒泡排序 | O(n²) | O(1) | 稳定 |
快速排序 | O(nlogn) | O(logn) | 不稳定 |
选择类排序
名称 | 时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|
简单选择排序 | O(n²) | O(1) | 不稳定 |
堆排序 | O(nlogn) | O(1) | 不稳定 |
归并类排序
名称 | 时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|
二路归并排序 | O(nlogn) | O(n) | 稳定 |
基数类排序
名称 | 时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|
基数排序 | O(d(n+r)) | O(r) | 稳定 |
总表1
名称 | 时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|
直接插入排序 | O(n²) | O(1) | 稳定 |
折半插入排序 | O(n²) | O(1) | 稳定 |
希尔排序 | O(n的1.3次方)~O(n²) | O(1) | 不稳定 |
冒泡排序 | O(n²) | O(1) | 稳定 |
快速排序 | O(nlogn) | O(logn) | 不稳定 |
简单选择排序 | O(n²) | O(1) | 不稳定 |
堆排序 | O(nlogn) | O(1) | 不稳定 |
二路归并排序 | O(nlogn) | O(n) | 稳定 |
基数排序 | O(d(n+r)) | O(r) | 稳定 |
总表2
名称 | 最好时间复杂度 | 平均时间复杂度 | 最坏时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
直接插入排序 | O(n) | O(n²) | O(n²) | O(1) | 稳定 |
折半插入排序 | O(nlogn) | O(n²) | O(n²) | O(1) | 稳定 |
希尔排序 | O(n的1.3次方) | O(n的1.3次方)~O(n²) | O(n²) | O(1) | 不稳定 |
冒泡排序 | O(n) | O(n²) | O(n²) | O(1) | 稳定 |
快速排序 | O(nlogn) | O(nlogn) | O(n²) | O(logn) | 不稳定 |
简单选择排序 | O(n²) | O(n²) | O(n²) | O(1) | 不稳定 |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 不稳定 |
二路归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 |
基数排序 | O(d(n+r)) | O(d(n+r)) | O(d(n+r)) | O(r) | 稳定 |
记忆
时间复杂度为O(n²):
- 简单排序:直接插入排序,折半插入排序,冒泡排序,简单选择排序
时间复杂度为O(nlogn):
- 复杂排序:希尔排序(可能),快速排序,堆排序
- 归并排序:二路归并排序
时间复杂度特殊:
- 基数排序
最好、平均和最坏时间复杂度相同:
- 简单选择排序
- 堆排序
- 二路归并排序
- 基数排序
最好和平均时间复杂度不同:
- 直接插入排序
- 折半插入排序
- 希尔排序
- 冒泡排序
最坏和平均时间复杂度不同:
- 希尔排序
- 快速排序
空间复杂度不为O(1):
- 快速排序
- 二路归并排序
- 基数排序
稳定:
- 简单排序:直接插入排序,折半插入排序,冒泡排序
- 归并排序,基数排序:二路归并排序
不稳定:
- 复杂排序:希尔排序,快速排序
- 选择类排序:简单选择排序,堆排序
一趟排序,能够确定元素的最终位置:
- 交换类排序:冒泡排序,快速排序
- 选择类排序:简单选择排序,堆排序
比较次数和初始序列无关:
- 折半插入排序
- 简单选择排序
- 二路归并排序
- 基数排序
移动次数和初始序列无关:
- 基数排序
排序趟数和初始序列有关:
- 交换类排序:冒泡排序,快速排序
应用
数据基本有序:最好时间复杂度小
- 直接插入排序
- 冒泡排序
数据规模小(<10000):简单排序
- 直接插入排序(性能最优;数据规模<=25)
- 折半插入排序(相对直接插入排序,适用数据规模更大)
- 冒泡排序
- 简单选择排序(相对冒泡排序,性能更优)
数据规模小,信息量大(如:数据是数十位的数字->占用空间大->移动时间长):
- 简单选择排序(移动次数少)
数据规模大:复杂排序
- 数据规模<=1000:希尔排序
- 数据随机分布:快速排序(性能最优;数据基本有序时,性能最差)
- 空间复杂度小;从大数据规模排序小部分数据:堆排序
- 稳定:归并排序
- 多关键字排序:基数排序
数据规模更大:外部排序
数据规模大,数据位数少:
- 基数排序
数据规模大,数据位数多:
- 最高位优先(MSD)的基数排序结合直接插入排序
其他:
- 堆排序只适用顺序存储方式
- 堆排序建堆时比较次数多,不适用数据规模小
- 归并排序尽量用迭代法而不是递归法
- 基数排序不适用实数类型
- 混合排序使用
外部排序
概念
- 内存作为工作/辅助空间,排序外存数据
- 算法相对复杂
- 时间=读写外存内存时间+内存排序时间+内存归并时间
- 时间->访问外存/输入/输出(I/O)次数
- 优化:减少访问外存/输入/输出(I/O)次数->减少初始归并段数,增多
流程
- 外存数据分段
- 段读入内存
- 段在内存排序,为初始归并段
- 初始归并段写回外存
- 初始归并段读入内存
- 初始归并段在内存归并,为归并段
- 归并段写回外存
另:
- 分段
- 排序
- 归并
排序方法
常用:
- 归并排序
子算法:
- 置换-选择排序
- 最佳归并树
- 败者树
置换-选择排序:
- 作用:排序时,生成初始归并段时不受内存限制;减少初始归并段数
- 外存段中,每记录读入内存
- 依据排序规则,选择一个最值生成初始归并段。不符合排序规则的最值等待生成下一初始归并段
- 初始归并段长度不同
最佳归并树:
- 作用:归并时,减少访问外存/输入/输出(I/O)次数;适应长度不同初始归并段的归并
- 生成哈夫曼树
- 权:初始归并段的数据规模
- 路径长度:归并次数
- 2:读和写
- 访问外存/输入/输出(I/O)次数 = 带权路径长度(WPL) × 2
败者树:
- 作用:排序和归并时,减少选最值的比较次数/时间复杂度;增多归并路数
对最小值败者树:
- 叶子结点:值为归并段的段首值,旁为归并段的序号
- 非叶子结点:值为归并段的序号,旁为归并段的段首值
- 构造:比较值,构造二叉树。败者(值大者)为双亲结点,胜者(值小者)为双亲结点的双亲结点
- 由树根结点的序号,取值写回外存,取另外值读入内存
- 调整:同构造过程
选择树:堆,败者树
时间复杂度:相对复杂
- 置换-选择排序中,每记录有两次访问外存/输入/输出(I/O)
- 每一趟归并,每记录有两次访问外存/输入/输出(I/O)
- 归并趟数:[log以k为底的m]上取整。k为归并路数,m为初始归并段数=外存记录数÷内存能容纳的记录数
- k路归并败者树:k个记录/叶子结点的二叉树
- k路归并败者树构造的时间复杂度:O(klogk)。k为归并路数
- k路归并败者树的树高:[log以2为底的k]上取整+1。k为归并路数(不包括树根结点层)
- 传统选最值的比较次数:k-1。k为归并路数
- k路归并败者树选最值的比较次数:[log以2为底的k]上取整。k为归并路数
- k路归并败者树选最值的时间复杂度:O(logk)。k为归并路数
空间复杂度:O(1)。未使用辅助空间
总结
“排序”学习提纲。
参考资料
- 《2023版数据结构高分笔记》主编:率辉
- 《2023年计算机组成原理考研复习指导》组编:王道论坛
- 《大话数据结构》作者:程杰
作者的话
- 感谢参考资料的作者/博主
- 作者:夜悊
- 版权所有,转载请注明出处,谢谢~
- 如果文章对你有帮助,请点个赞或加个粉丝吧,你的支持就是作者的动力~
- 文章在描述时有疑惑的地方,请留言,定会一一耐心讨论、解答
- 文章在认识上有错误的地方, 敬请批评指正
- 望读者们都能有所收获
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 夜悊的技术小宅!