各种排序算法,用C语言实现
Sort Algorithms
你心爱的各种排序算法,用 C 语言实现。
我们下面将要实现的算法,全部准循这个接口:
1 | // sort 对数组 A 的前 n 个元素进行原址排序 |
直接插入排序
遍历,往前找到合适的位置,逐个元素后移腾出空间,插入进去。
复杂度:
- 时间 $O(n^2)$
- 空间 $O(1)$
1 | void |
二分插入排序
就是直接插入里往前找合适位置那里用个二分查找。
复杂度:
- 时间:较好 $O(n \log(n))$,较坏 $O(n^2)$,平均 $O(n^2)$
- 空间 $O(1)$
1 | void |
Shell 排序
递减增量(gap)的排序算法(非稳定)。就是分好几轮排序,每轮里在隔 gap 个的序列里调整插入。
空间复杂度是 $O(1)$,时间复杂度依赖于步长序列。
1 | void |
foreach_gaps
遍历增量序列,将步长值放到 gap 中。可以选用多种步长序列(注意步长最后一步务必为 1):
Shell 步长序列: $n/2^i$,最坏情况下时间复杂度 $O(n^2)$
1
2
3
for (gap = n >> 1; gap > 0; gap >>= 1) \
fPapernov-Stasevich 步长序列:$2^k-1$,最坏情况下时间复杂度 $O(n^{\frac{3}{2}})$
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59// papernov_stasevich_start find the max n s.t. (2^k+1) < n
// return 2^k+1
static inline int
ps_start(int n)
{
int k = 1, nn = n - 1;
while (nn > 0 && nn != 1) {
nn >>= 1;
k <<= 1;
}
return k + 1;
}
// 2^k+1, ..., 65, 33, 17, 9, 5, 3, 1
static inline int
ps_next(int gap)
{
switch (gap) {
case 1:
return -1; // to stop
case 3:
return 1;
default:
return ((gap - 1) >> 1) + 1;
}
}
for (gap = ps_start(n); gap > 0; gap = ps_next(gap)) \
f;
还有很多种步长,这个 Papernov-Stasevich 也不是最好的。具体维基百科有介绍。
## 冒泡排序
从后向前形成序列(`i=n-1...1`):每次从 0 检查至 i-1,后一个比前一个大的就交换一下:冒泡。
- 时间复杂度:最坏 $O(n^2)$,最好 $O(n)$
- 空间复杂度:$O(1)$
```c
void
bubble_sort(int A[], int n)
{
for (int i = n - 1; i > 0; i--) {
int swap_flag = 0;
for (int j = 0; j < i; j++) {
if (A[j] > A[j + 1]) {
swap(A, j, j + 1);
swap_flag = 1;
}
}
if (!swap_flag) { // 这一轮都没交换,已经有序了,提前结束
return;
}
}
}
其中的 swap 容易实现:
1 | // swap 原址交换数组 A 中下标 i 与 j 的值 |
快速排序
对 A[l...r]
(闭区间) 快速排序:
- 选个轴,序列中比轴小的放轴左边,比轴大的在其右边
- 然后把轴左右分别做两个子序列,递归。
复杂度:
- 时间复杂度:最好 $O(n\log(n))$,最坏 $O(n^2)$,平均 $O(n \log(n))$
- 空间复杂度 $O(log(n))$
1 | void |
其中,partition
做快排的交换工作:
- 以第一个元素
A[l]
为轴 - 序列中比轴小的放轴左边,比轴大的在其右边
- 返回轴的索引
这个写了两种实现,一种是 partition_place,一种是 partition_swap,无论是从好理解还是从方便记,我都喜欢后者:
1 | int |
1 | int |
选择排序
从下标 0 到 n,每个位置 i
选择 A[i...n]
(闭区间)里最小的一个放上去。
- 时间复杂度 $O(n^2)$
- 空间复杂度 $O(1)$
1 | void |
堆排序
把序列搞成个大根堆(根结点比页大的那种),然后依次出根节点,重新调整堆。
- 时间复杂度 $O(n \log(n))$
- 空间复杂度 $O(1)$
1 | void |
用 CLRS 里面的 maxHeapify,更容易理解:
1 | void |
归并排序
递归完成左右两半的排序,然后归并
- 时间复杂度 $O(n \log(n))$
- 空间复杂度 $O(n)$
1 | void |
归并:把数组的 A[l: m+1]
和 A[m: r+1]
两个已排序部分按升序合并:
- 先备份数组
- 顺序从左右两半中选出较小者,放入原数组
- 完成归并
需要辅助数组,空间复杂度 $O(n)$
1 | void |
完整代码
完整代码整合在这个 gist 中:
参考
- CLRS, 3e
- https://juejin.cn/post/6997172275787071518
- 天勤
- 王道
- 还有其他好多,忘了。。
大概就这样吧,我对排序算法什么的,没有多少热情,并不想写这篇文章的。算法测试用例写的也单一,所以并不保证正确,如有谬误,还望海涵。