排序算法总结
6.1 基本概念
6.1.1 目标
使关键字有序
6.1.2 分类
内部排序
数据元素在排序期间全在内存中
外部排序
排序过程中,数据不断在内外存之间移动
6.1.3 稳定性
经过排序,关键字相同的元素保持原先的先后顺序
6.1.4 两种操作
比较
确定关键字先后关系
移动
移动元素,达到序列有序
6.1.5 一趟排序
对所有未处理的元素处理一遍
6.2 内部排序
6.2.1 总结归纳
稳定性
插冒归基 稳
- 折半插入
- 直接插入
- 冒泡排序
- 归并排序
- 基数排序
是稳定的
不稳定排序算法有
- 希尔排序
- 快排
- 简单选择排序
- 堆排序
适用链式存储
插冒选归基
- 直接插入排序
- 冒泡排序
- 简单选择排序
- 归并排序
- 基数排序
不会形成有序子序列
快排
时间复杂度
时间复杂度与初始状态无关
一堆()乌龟()选()基友()
- 简单选择 $O(n^2)$
- 堆排序 $O(nlogn)$
- 归并排序 $O(nlogn)$
- 基数排序 $O(rd(n+r))$
比较次数与初始状态无关
选择排序
与初始状态有关
插入排序
最好:有序
直接 $O(n)$
折半 $O(nlogn)$
最坏:逆序
交换排序
冒泡
最好:有序 $O(n)$
最坏:逆序 $O(n^2)$
快排
最好:枢轴中分 $O(nlogn)$
最坏:基本有序,逆序 $O(n^2)$
冒泡排序趟数与初始状态有关
为O(nlogn)
- 快排
- 堆排
- 归并
空间复杂度
O(1)
- 直接插入
- 折半插入
- 希尔排序
- 冒泡排序
- 简单选择
- 堆排序
O(logn)
快排最好
O(n)
- 快排最坏
- 归并排序
- 基数最坏O(r)
适用场景
规模
取第k小
- k趟冒泡排序
- k趟简单选择排序
- k趟堆排序
形成部分有序序列
- 直接插入
- 冒泡排序
- 快排
- 简单选择
- 堆排序
6.2.2 五大类排序
graph LR; A[排序]-->B[插入排序] A-->C[交换排序] A-->D[选择排序] A-->E[归并排序] A-->F[基数排序] B-->直接插入排序 B-->折半插入排序 B-->希尔排序 C-->冒泡排序 C-->快速排序 D-->简单选择排序 D-->堆排序
插入排序
思路
在最后一趟之前,所有元素都可能不在最终位置上
直接插入排序
思路
实现
1 | //从小到大排序 |
特点
折半插入
直接插入查找插入位置时遍历改为折半插入
实现
1 | void Binary_InsertSort(ElemType A[],int n){ |
特点
希尔排序
理解:由于直接插入排序适合大致有序的序列,故可对数据进行预处理,再进行直接插入排序
思路
- 每隔一个步长取一个数
- 步长为两次取数之间的间隔数
特点
选择排序
思路:选最值,作为有序子序列的端点
简单选择排序
思路
实现
1 | void selectSort(ElemType A[],int n){ |
特点
堆排序
思路
存储结构
步骤
建初始堆
从最后一个非叶结点开始调整 $\frac{n}{2}或\frac{n}{2}+1 \sim 1$
取根与堆的最后一个元素交换
重新调整堆,重复上述步骤
操作
删除
- 从上到下比较
- 调用一次
HeapAdjust
,从根开始调整,两个结点先比,根据结果交换 - 时间复杂度 $O(h) = O(logn)$
插入
从下往上比
比较范围:本子树
将 $K_{n+1}$ 作为叶子结点插入末尾,重复将 $K_{n+1}$ 与其新双亲比较,直至 $K_{n+1}$ 满足堆的特点
实现
1 | void BuildMaxHeap(ElemType A[],int n){ |
特点
交换排序
冒泡排序
思路
每一趟都将一个最大或最小元素放在最终位置上
实现
1 | void BubbleSort(int arr[],int n){ |
特点
快速排序
思路
冒泡排序的改进:分治法
不断移动枢轴位置,寻找平衡点
整体流程
一趟排序
实现
1 | int Partition(ElemType A[],int low,int high){ |
特点
归并排序
思路
分解
将含n个元素的带排序表分为 $\frac{n}{2}$ 元素的子表,分别对两个子表排序
合并
合并两个已排序的子表得到结果
实现
1 | void Merge(ElemType A[],int low,int mid,int high){ |
特点
基数排序
不是基于比较和查找
思路
每组关键字,按同一逻辑排序
先排主关键字,再排次关键字
先排主关键字,则主关键字排好后,无法处理次关键字相同的情况,实际情况 先排次关键字,再排主关键字
如:010 102 110 212基数排序
先主后次
显然不能先主后次
先次后主
特点
桶排序(计数排序)
适用于:对一定范围内的整数排序
1 |
|
6.3 外部排序
6.3.1 存储空间
K路归并排序,内存中一个输出缓存区,K个输入缓存区,对r个元素进行排序
6.3.2 过程
1. 生成k个初始归并段
把K个归并段的块读入K个输入缓冲区
对每个输入缓冲区中的L个记录进行内部排序,组成K个有序的初始归并段
2. 进行S趟K路归并 $S=\lceil log_kr\rceil$
用归并排序的思想从K个归并段中选出各段最小记录,暂存到输出缓冲区
- 当某一输入缓冲区为空,立马读入新块
- 缓冲区不空才进行下一轮比较
输出缓冲区满,写出外存
6.3.3 时间开销
读写外出:归并多少趟,则读入多少次
内部排序:可优化段数,但读写IO次数不变
块内排序:构造初始归并段花费时间
内部归并:K路中选择最值放入输出缓冲区
6.3.4 优化
增加归并路数
代价
增加响应的输入缓冲区
每次从k个归并段中选出一个最小元素需要 $k-1$ 次关键字比较
内部归并优化
败者树,减少关键字比较次数
K路有K个叶
- $树高=比较次数=\lceil log_2K \rceil$
总比较次数
- $S(n-1)\lceil log_2k \rceil = \lceil log_kr \rceil(n-1)\lceil log_2k \rceil = \lceil log_2r \rceil(n-1)$
k路平衡归并
最多k个段并为1个
每一趟若有m个段,处理完后会有 $\lceil \frac{m}{r} \rceil$