排序算法总结
概述
可根据数据结构和算法动态可视化网站,更加形象地学习。
直接插入排序
算法思想
每次将一个待排序的记录插入到已排序好的序列中。
(49) 38 65 97 76 13 27 49
(38 49) 65 97 76 13 27 49
(38 49 65) 97 76 13 27 49
(38 49 65 97) 76 13 27 49
(38 49 65 76 97) 13 27 49
(13 38 49 65 76 97) 27 49
(13 27 38 49 65 76 97) 49
(13 27 38 49 49 65 76 97)()表示已排好序的部分
代码实现
1 | //直接插入排序 |
性能分析
空间复杂度: $O(1)$
时间复杂度
最好时间复杂度(全部有序):$O(n)$
共 $n-1$ 趟,每趟只需比对一次关键字,不用移动元素。
最坏时间复杂度(全部逆序):$O(n^2)$
共 $n-1$ 趟,每趟(如第 $i$ 趟)对比 $i$ 次,移动 $i$ 次。
平均时间复杂度:$O(n^2)$
稳定性
稳定。每次插入元素都是从前往后比较移动,所以不会出现相同元素相对位置变化。
- 适用性:顺序表和链表
希尔排序
算法思想
希尔排序是插入排序的一种。也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。
每轮把记录按下标的一定增量分组 $L[i, i+d, i+2d,…,i+kd]$ ,对每组直接插入排序;
逐渐减少增量,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
49 38 65 13 76 13 27 57 (d = 4)
49 13 27 13 76 38 65 57 (d = 2)
27 13 49 13 65 38 76 57 (d = 1)
13 13 27 38 49 57 65 76
代码实现
1 | //希尔排序 |
性能分析
空间复杂度: $O(1)$
时间复杂度
最坏时间复杂度 $O(n^2)$
最好时间复杂度(全部有序):$O(n)$
当n在某个特定范围时,可达 $O(n^{1.3})$
稳定性
不稳定。如果两相同元素划分到不同子表,可能会改变相对位置。
适用性:顺序表
冒泡排序
算法思想
每趟从前往后(或从后往前)两两相邻元素比较,如逆序,则交换
每趟可冒泡出最大的元素到最后
(1)
49 38 65 97 76 13 27 49
38 49 65 97 76 13 27 49
38 49 65 97 76 13 27 49
38 49 65 97 76 13 27 49
38 49 65 76 97 13 27 49
38 49 65 76 13 97 27 49
38 49 65 76 13 27 97 49
38 49 65 76 13 27 49 (97)(2)
38 49 65 76 13 27 49 (97)
38 49 65 76 13 27 49 (97)
…
38 49 65 13 27 49 (76 97)……
(n-1)
(13 27 38 49 49 65 76 97)
代码实现
1 | //冒泡排序 |
性能分析
空间复杂度: $O(1)$
时间复杂度
最好时间复杂度(全部有序):$O(n)$
只需冒泡一趟,比较 $n$ 次,交换 $0$ 次,时间复杂度为$O(n)$
最坏时间复杂度(全部逆序): $O(n^2)$
共冒泡 $n-1$ 趟
第 $i$ 趟,比较 $n-i-1$ 次,交换 $n-i-1$ 次
比较次数 = 交换次数 = $(n-1)+(n-2)+…+1 =n(n-1)/2$
时间复杂度 $O(n^2)$
平均时间复杂度: $O(n^2)$
稳定性
稳定。只在相邻逆序时交换,不改变相同元素的相对位置。
适用性:顺序表和链表
快速排序
算法思想
每趟排序确定一个元素 $x$ 的最终位置,将数据分为$
(1):确定49的位置,与49比较,左边部分小于49,右边部分大于49
49 | 38 | 65 | 97 | 76 | 13 | 27 | 49 |
---|---|---|---|---|---|---|---|
49 | |||||||
38 | 65 | 97 | 76 | 13 | 27 | 49 | |
low | high | ||||||
49 | |||||||
38 | 65 | 97 | 76 | 13 | 27 | 49 | |
low | high | ||||||
49 | |||||||
27 | 38 | 65 | 97 | 76 | 13 | 49 | |
low | high | ||||||
49 | |||||||
27 | 38 | 65 | 97 | 76 | 13 | 49 | |
low | high | ||||||
49 | |||||||
27 | 38 | 65 | 97 | 76 | 13 | 49 | |
low | high | ||||||
49 | |||||||
27 | 38 | 97 | 76 | 13 | 65 | 49 | |
low | high | ||||||
49 | |||||||
27 | 38 | 97 | 76 | 13 | 65 | 49 | |
low | high | ||||||
49 | |||||||
27 | 38 | 13 | 97 | 76 | 65 | 49 | |
low | high | ||||||
49 | |||||||
27 | 38 | 13 | 97 | 76 | 65 | 49 | |
low | high | ||||||
49 | |||||||
27 | 38 | 13 | 76 | 97 | 65 | 49 | |
low | high | ||||||
49 | |||||||
27 | 38 | 13 | 76 | 97 | 65 | 49 | |
low | high | ||||||
49 | |||||||
27 | 38 | 13 | 76 | 97 | 65 | 49 | |
l=h | |||||||
27 | 38 | 13 | 49 | 76 | 97 | 65 | 49 |
<49 | l=h | >49 |
再分别对左右两边 $[27, 38, 13]$ 、 $[76, 97, 65, 49]$ 进行递归排序。
代码实现
1 | //快速排序 |
性能分析
空间复杂度
需要调用递归栈,相当于分别用n个枢轴建立一个二叉树,空间复杂度即为递归层数,也就是二叉树高度。
最好空间复杂度:$O(log_2n)$
枢轴划分均匀,二叉树为完全二叉树,高度为 $log_2n$
最坏时间复杂度 $O(n)$
枢轴划分极不均匀,如每个都划分成只有大于pivot或小于pivot,二叉树每个节点的度都为1,高度为 $n$
时间复杂度
时间复杂度为 $O(n*递归层数)$
最好时间复杂度(划分均匀):$O(nlog_2N)$
最坏时间复杂度(划分极不均匀): $O(n^2)$
平均时间复杂度: $O(nlog_2N)$
稳定性
不稳定。交换时,可能把相对位置更后的元素交换到更前的位置。
| 49 | 38 | 65 | 16 | 76 | 13 | 57 | 16 |
|——|——|——|:—-|:—-|:—-|:—-|:—————|
| 49 | | | | | | | |
| | 38 | 65 | 16 | 76 | 13 | 27 |16|
| low| | | | | | | high |
| | | | | | | | 49 |
| 16 | 38 | 65 | 16 | 76 | 13 | 27 | |
| low| | | | | | | high |适用性:顺序表
简单选择排序
算法思想
最简单的一种排序方法。每次找到最小的那个元素,和第一个元素交换。
49 38 65 97 76 13 27 49
(13) 38 65 97 76 49 27 49
(13 27) 65 97 76 49 38 49
(13 27 38) 97 76 49 65 49
(13 27 38 49) 76 97 65 49
(13 27 38 49 49) 97 65 76
(13 27 38 49 49 65) 97 76
(13 27 38 49 49 65 76) 97
(13 27 38 49 49 65 76 97)
代码实现
1 | //简单选择排序 |
性能分析
空间复杂度:$O(1)$
时间复杂度:$O(n^2)$
无论是有序、无序、乱序,都需要 $n-1$ 趟选出最小值,第i趟选最小值都需要对比 $n-i$ 次。即 $(n-1)+(n-2)+…+1 = n(n-1)/2$
稳定性
不稳定。交换时,可能把相对位置更前的元素换到后面去。
2 2 1
1 2 2
1 2 2适用性:顺序表和链表
堆排序
算法思想
先建立大根堆/小根堆,再基于大根堆/小根堆进行排序。
二叉树的顺序存储
完全二叉树可以顺序存储为序列,如下面序列和完全二叉树的对应
t[0] | t[1] | t[2] | t[3] | t[4] | t[5] | t[6] | t[7] | t[8] | t[9] |
---|---|---|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
graph TD A((1)) B((2)) C((3)) D((4)) E((5)) F((6)) G((7)) H((8)) I((9)) A---B; A---C; B---D; B---E; C---F; C---G; D---H; D---I;
对于共有 $n$ 个结点完全二叉树,序号为 $i$ 结点
$i$ 的左孩子: $2i$
$i$ 的右孩子: $2i+1$
$i$ 的父结点: $\lfloor i/2 \rfloor$
$i$ 所在层次: $\lfloor log_2(n+1) \rfloor$
$i$ 是否有左孩子: $2i \le n?$
$i$ 是否有右孩子: $2i+1 \le n?$
$i$ 是否是叶子结点: $i \ge \lfloor n/2 \rfloor?$
堆的定义
堆分为大根堆/小根堆,即更大的为根/更小的为根
大根堆:根 >= 左、右
| | 87 | 45 | 78 | 32 | 17 | 65 | 53 | 9 |
|—-|—-|—-|—-|:—|:—|:—|:—|:—|
<pre class="mermaid"> graph TD
A((87))
B((45))
C((78))
D((32))
E((17))
F((65))
G((53))
H((9))
A---B;
A---C;
B---D;
B---E;
C---F;
C---G;
D---H;</pre>
小根堆:根 <= 左、右
| | 9 | 45 | 17 | 65 | 53 | 32 | 87 | 78 |
|—-|—-|—-|—-|:—|:—|:—|:—|:—|
<pre class="mermaid"> graph TD
A((9))
B((45))
C((17))
D((65))
E((53))
F((32))
G((87))
H((78))
A---B;
A---C;
B---D;
B---E;
C---F;
C---G;
D---H;</pre>
堆的建立
以大根堆为例,
把所有的非叶子结点检查一遍,如果不满足大根堆要求,则进行调整。