乱扯排序算法

排序算法是数据结构课程中的一个很重要的部分,虽然在实际的编码中并没有什么卵用–但是我们还是要扯一扯这些算法←_←
本文所涉及的排序算法均为单线程的, 基于冯-诺依曼架构的算法,且采用C++实现
于是让我们进入正题吧

串行排序

冒泡排序

以交换次数多而闻名的排序算法,时间复杂度为稳定的$O(n^2)$,额外空间为$O(1)$,是很多教材(谭浩强)介绍的第一个排序算法,原理简单而反人类。

1
2
3
4
5
6
7
8
9
10
void () {
for (int i = 1; i < n; ++i) {
changed = 0;
for (int j = n; j > i; --j)
if (num[j] < num[j - 1])
swap(num[j], num[j - 1]), changed = 1;
if (!changed)
break;
}
}

值得注意的是,当某一次遍历后没有交换元素,则说明数组已完成排序

选择排序

大概是最符合直觉的排序方法之一了……时间复杂度为$O(n^2)$,额外空间$O(1)$

1
2
3
4
5
6
7
8
9
10
for (i = n; i > 1; --i) {
maxnum = f[1], maxi = 1;
for (j = 2; j <= i; j++) {
if (f[j] > maxnum) {
maxnum = f[j];
maxi = j;
}
}
swap(f[maxi], f[i]);
}

选择法可以先找出最大的N个数

插入排序

算法导论中以纸牌排序为例引入了这个算法,也是很符合直觉的排序方法,最坏时间复杂度为$O(n^2)$,最好为$O(n)$,平均$O(n^2)$,额外空间为$O(1)$

1
2
3
4
5
6
for (int i = 1; i <= n; ++i) {
scanf("%d", &x);
for (j = i; j > 1 && f[j - 1] > x; --j)
f[j] = f[j - 1];
f[j] = x;
}

插入排序可以在数据输入的同时进行排序

归并排序

本文所涉及的归并排序算法为二路归并排序,时间复杂度为$O(nlg n)$,额外空间为$O(n)$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void merge_sort (int l, int r) {
if (l >= r) return;
int m = (l + r) >> 1;
merge_sort(l, m);
merge_sort(m + 1, r);
int i = l, j = m + 1, c = l;
while (i <= m || j <= r) {
if (j > r || (i <= m && f[i] <= f[j]))
t[c++] = f[i++];
else
t[c++] = f[j++];
}
memcpy(f + l, t + l, sizeof(int) * (r - l + 1));
}

归并排序不仅可以对数组(随机读取器)排序,也可以对链表排序,还可以对文件排序

堆排序

是一种数据结构(不是内存中的那个),通常情况下指二叉堆,可以作为优先队列使用。堆排序的时间复杂度为$O(nlg n)$,额外空间为$O(1)$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void down(int i) {
int p, l, r;
while(true) {
p = i, l = i << 1, r = l + 1;
if (l <= size && f[p] < f[l]) p = l;
if (r <= size && f[p] < f[r]) p = r;
if (i == p) break;
swap(f[i], f[p]);
i = p;
}
}
void build() {
for (int i = size >> 1; i >= 1; --i)
down(i);
}
void heapsort() {
size = n;
build();
while(size > 1) {
swap(f[1], f[size]);
--size;
down(1);
}
}

堆排序可以看作选择排序的改进(优先队列的应用),将$O(n)$的选择时间降为$O(lg n)$

快速排序

快速排序(没错就是这个名字)是一种快速的排序算法(废话),应用非常广泛,包括但不限于考试出题(比如时间复杂度分析,递归改为栈+迭代)。快速排序的最佳时间复杂度为$O(nlg n)$,最差达到了$O(n^2)$,平均为$O(nlg n)$,而且(据说)常数小,运行快,一般不会碰到最坏的情况,在工业界使用率很高。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void quicksort(int f[], int low, int high) {
int k = (high - low) / 2 + low;
int x = f[k], i = low, j = high;
f[k] = f[i];
while (i < j) {
while (i < j && f[j] >= x) --j;
f[i] = f[j];
while (i < j && f[i] < x) ++i;
f[j] = f[i];
}
f[i] = x;
if (i - low > 1)
quicksort(f, low, i - 1);
if (high - i > 1)
quicksort(f, i + 1, high);
}

非递归版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void quicksort() {
std::stack<int> lowStack, highStack;
lowStack.push(1);
highStack.push(n);
while (!lowStack.empty()) {
int low = lowStack.top();lowStack.pop();
int high = highStack.top();highStack.pop();
int k = (high - low) / 2 + low;
int x = f[k], i = low, j = high;
f[k] = f[i];
while (i < j) {
while (i < j && f[j] >= x) --j;
f[i] = f[j];
while (i < j && f[i] < x) ++i;
f[j] = f[i];
}
f[i] = x;
if (i - low > 1) lowStack.push(low), highStack.push(i - 1);
if (high - i > 1) lowStack.push(i + 1), highStack.push(high);
}
}

乱入haskell版,也许更容易理解

1
2
quicksort [] = []
quicksort (x:xs) = quicksort (filter (< x) xs) ++ [x] ++ quicksort (filter (>= x) xs)

PHP中的quicksort

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
60
61
62
63
ZEND_API void zend_qsort(void *base, size_t nmemb, size_t siz, compare_func_t compare, swap_func_t swp)
{
void *begin_stack[QSORT_STACK_SIZE];
void *end_stack[QSORT_STACK_SIZE];
register char *begin;
register char *end;
register char *seg1;
register char *seg2;
register char *seg2p;
register int loop;
size_t offset;
begin_stack[0] = (char *) base;
end_stack[0] = (char *) base + ((nmemb - 1) * siz);
for (loop = 0; loop >= 0; --loop) {
begin = begin_stack[loop];
end = end_stack[loop];
while (begin < end) {
offset = (end - begin) >> Z_L(1);
swp(begin, begin + (offset - (offset % siz)));
seg1 = begin + siz;
seg2 = end;
while (1) {
for (; seg1 < seg2 && compare(begin, seg1) > 0;
seg1 += siz);
for (; seg2 >= seg1 && compare(seg2, begin) > 0;
seg2 -= siz);
if (seg1 >= seg2)
break;
swp(seg1, seg2);
seg1 += siz;
seg2 -= siz;
}
swp(begin, seg2);
seg2p = seg2;
if ((seg2p - begin) <= (end - seg2p)) {
if ((seg2p + siz) < end) {
begin_stack[loop] = seg2p + siz;
end_stack[loop++] = end;
}
end = seg2p - siz;
}
else {
if ((seg2p - siz) > begin) {
begin_stack[loop] = begin;
end_stack[loop++] = seg2p - siz;
}
begin = seg2p + siz;
}
}
}
}

以上代码出自PHP7,但是貌似已经废弃,被另一个quicksort代替了

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
ZEND_API void zend_sort(void *base, size_t nmemb, size_t siz, compare_func_t cmp, swap_func_t swp)
{
while (1) {
if (nmemb <= 16) {
zend_insert_sort(base, nmemb, siz, cmp, swp);
return;
} else {
char *i, *j;
char *start = (char *)base;
char *end = start + (nmemb * siz);
size_t offset = (nmemb >> Z_L(1));
char *pivot = start + (offset * siz);
if ((nmemb >> Z_L(10))) {
size_t delta = (offset >> Z_L(1)) * siz;
zend_sort_5(start, start + delta, pivot, pivot + delta, end - siz, cmp, swp);
} else {
zend_sort_3(start, pivot, end - siz, cmp, swp);
}
swp(start + siz, pivot);
pivot = start + siz;
i = pivot + siz;
j = end - siz;
while (1) {
while (cmp(pivot, i) > 0) {
i += siz;
if (UNEXPECTED(i == j)) {
goto done;
}
}
j -= siz;
if (UNEXPECTED(j == i)) {
goto done;
}
while (cmp(j, pivot) > 0) {
j -= siz;
if (UNEXPECTED(j == i)) {
goto done;
}
}
swp(i, j);
i += siz;
if (UNEXPECTED(i == j)) {
goto done;
}
}
done:
swp(pivot, i - siz);
if ((i - siz) - start < end - i) {
zend_sort(start, (i - start)/siz - 1, siz, cmp, swp);
base = i;
nmemb = (end - i)/siz;
} else {
zend_sort(i, (end - i)/siz, siz, cmp, swp);
nmemb = (i - start)/siz - 1;
}
}
}
}

PHP中的新版sort是一种混合排序,在待排序个数小于16个的时候调用插入排序

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
ZEND_API void zend_insert_sort(void *base, size_t nmemb, size_t siz, compare_func_t cmp, swap_func_t swp) {
switch (nmemb) {
case 0:
case 1:
break;
case 2:
zend_sort_2(base, (char *)base + siz, cmp, swp);
break;
case 3:
zend_sort_3(base, (char *)base + siz, (char *)base + siz + siz, cmp, swp);
break;
case 4:
{
size_t siz2 = siz + siz;
zend_sort_4(base, (char *)base + siz, (char *)base + siz2, (char *)base + siz + siz2, cmp, swp);
}
break;
case 5:
{
size_t siz2 = siz + siz;
zend_sort_5(base, (char *)base + siz, (char *)base + siz2, (char *)base + siz + siz2, (char *)base + siz2 + siz2, cmp, swp);
}
break;
default:
{
char *i, *j, *k;
char *start = (char *)base;
char *end = start + (nmemb * siz);
size_t siz2= siz + siz;
char *sentry = start + (6 * siz);
for (i = start + siz; i < sentry; i += siz) {
j = i - siz;
if (!(cmp(j, i) > 0)) {
continue;
}
while (j != start) {
j -= siz;
if (!(cmp(j, i) > 0)) {
j += siz;
break;
}
}
for (k = i; k > j; k -= siz) {
swp(k, k - siz);
}
}
for (i = sentry; i < end; i += siz) {
j = i - siz;
if (!(cmp(j, i) > 0)) {
continue;
}
do {
j -= siz2;
if (!(cmp(j, i) > 0)) {
j += siz;
if (!(cmp(j, i) > 0)) {
j += siz;
}
break;
}
if (j == start) {
break;
}
if (j == start + siz) {
j -= siz;
if (cmp(i, j) > 0) {
j += siz;
}
break;
}
} while (1);
for (k = i; k > j; k -= siz) {
swp(k, k - siz);
}
}
}
break;
}
}

在插入排序中,当待排序个数小于等于5时分别有对应的特化

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
static inline void zend_sort_2(void *a, void *b, compare_func_t cmp, swap_func_t swp) {
if (cmp(a, b) > 0) {
swp(a, b);
}
}
static inline void zend_sort_3(void *a, void *b, void *c, compare_func_t cmp, swap_func_t swp) {
if (!(cmp(a, b) > 0)) {
if (!(cmp(b, c) > 0)) {
return;
}
swp(b, c);
if (cmp(a, b) > 0) {
swp(a, b);
}
return;
}
if (!(cmp(c, b) > 0)) {
swp(a, c);
return;
}
swp(a, b);
if (cmp(b, c) > 0) {
swp(b, c);
}
}
static void zend_sort_4(void *a, void *b, void *c, void *d, compare_func_t cmp, swap_func_t swp) {
zend_sort_3(a, b, c, cmp, swp);
if (cmp(c, d) > 0) {
swp(c, d);
if (cmp(b, c) > 0) {
swp(b, c);
if (cmp(a, b) > 0) {
swp(a, b);
}
}
}
}
static void zend_sort_5(void *a, void *b, void *c, void *d, void *e, compare_func_t cmp, swap_func_t swp) {
zend_sort_4(a, b, c, d, cmp, swp);
if (cmp(d, e) > 0) {
swp(d, e);
if (cmp(c, d) > 0) {
swp(c, d);
if (cmp(b, c) > 0) {
swp(b, c);
if (cmp(a, b) > 0) {
swp(a, b);
}
}
}
}
}

有趣的是,这一段新版的排序算法衍生自llvm中的std::sort(见原代码注释)

Introsort(内省排序)

Introsort was invented by David Musser in Musser (1997), in which he also introduced introselect, a hybrid selection algorithm based on quickselect (a variant of quicksort), which falls back to median of medians and thus provides worst-case linear complexity, which is optimal. Both algorithms were introduced with the purpose of providing generic algorithms for the C++ Standard Library which had both fast average performance and optimal worst-case performance, thus allowing the performance requirements to be tightened.
Introsort没有被写进教科书里,但这并不能说明它不强大。Introsort是C++标准模版库(STL)的内置排序算法,应用非常广泛。

wikipedia介绍的introsort非常简单,伪代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
procedure sort(A : array):
let maxdepth = ⌊
(length(A))⌋ × 2
introsort(A, maxdepth)
procedure introsort(A, maxdepth):
n ← length(A)
if n ≤ 1:
return // base case
else if maxdepth = 0:
heapsort(A)
else:
p ← partition(A) // assume this function does pivot selection, p is the final position of the pivot
introsort(A[0:p], maxdepth - 1)
introsort(A[p+1:n], maxdepth - 1)

我们可以很方便的将其”翻译”为C++,这里不再赘述

Timsort

Timsort是21世纪才被发明出来的一种排序方法,但是很快就成为了PythonJava的御用排序算法,可见其强大。
但是Timsort源码非常长,这里就不贴出来了,详情可见cpython/Objects/listobject.c

计数排序

计数排序是非常直观的一种排序方案,它不是基于比较的,所以突破了$O(nlg n)$的界定。

基数排序

另一种不是基于比较的排序算法

1
2
3
4
5
6
7
8
9
10
11
12
13
for (base = 1; base < 100000; base *= 10) {
while(!num.empty()) {
int top = num.front();
num.pop();
q[top / base % 10].push(top);
}
for (int i = 0; i < 10; ++i)
while(!q[i].empty()) {
int top = q[i].front();
q[i].pop();
num.push(top);
}
}

串行排序就先写到这里,虽然卵用没有但是水了一片文章也是极好的XD。

并行排序

由于多核计算机的发展,并行计算可以做到很多串行难以想象的事情。对多核的高效使用可以减少很多的计算时间。

由于对并行计算理解很浅,以下内容基本是胡说的。

并行快速排序

仔细观察快速排序对左右两个子数组的排序,发现它们实际上是独立的,完全可以同时处理。作为一个例子,我们可以使用多线程来并行排序,

1
由于相关法律法规,本代码暂不显示

并行归并排序

与快速排序不同,归并排序要求我们等待左右两部分排序完成再将它们合并

1
没有代码

睡眠排序

嗯,只是一个玩笑,不过睡眠排序真的可以按顺序输出所给的数字
为了方便我们这里就不写C++了(路人:说的好像你上面两个就有C++一样),而是采用简单的JavaClojure来实现

1
2
(defn sleep [x] (do (Thread/sleep (* x 500)) (println x)))
(pmap sleep nums)

排序网络

我就放在这里,这个我真的不懂


本博的原创作品作品采用知识共享署名 2.5 中国大陆许可协议 进行许可,欢迎转载,但转载请注明出处,并保持转载后文章内容的完整。
本文链接:http://fallenwood.github.io/2016/02/28/tale-of-sorting-algorithms/