冒泡排序 每执行一次外层循环, 当前最大的数就会下沉到数组末尾. 很简单, 但效率低下. 复杂度 `O(n^2)`.
插入排序
第 i 步循环时, 子数组 L[0..i-1]
已经有序, 只需将
L[i]
插入到合适位置.
时间复杂度 `O(n^2)`.
当 L
初始即有序时, 时间复杂度为 `O(n)`.
Shell 排序 时间复杂度优于 `O(n^2)`. 它由插入排序改造而来, 隐含的常数很小, 往往有不俗的成绩.
快速排序
次序统计量 求第 k 小的元素 (最小的元素是第 0 小). 和快速排序使用相同的 partition 子过程. 但分治时只走一个分支, 平均时间复杂度为 `O(log n)`.
链式基数排序
对 N
个 RADIX
进制的 KEYS
位数进行排序, 这些数字的每一位数放在数组 key
中,
以 next
指针链接起来.
算法按低位优先格式依次对各关键字进行分配和收集, 经过 KEYS
轮排序后完成.
smallint key[N+1][KEYS] # 关键字, 取值为 0..RADIX-1 int next[N+1] # 下一结点的下标 int first[RADIX] # RADIX 个子表的头 int last[RADIX] # RADIX 个子表的尾 # 按第 i 位数将各数字链成 RADIX 个子表, 每个表中 key[*][i] 值相同 # first[j], last[j] 指向各子表的第一个和最后一个记录 distribute(i): # 清空 RADIX 个子表 for j = 0 to RADIX-1: first[j] = 0 # 从头结点起遍历静态链表, 将结点打散重新链接 p = next[0] while p: d = key[p][i] # 取出第 i 位数 if first[d]: # 当第 d 个子表不空 next[last[d]] = p # 接到子表尾 else: first[d] = p # 作为子表头 last[d] = p # 更新子表尾指针 p = next[p] # 按分配结果依次链接各子表 collect(i): tail = 0 for j = 0 to RADIX-1: if first[j]: next[tail] = first[j] tail = last[j] next[tail] = 0 radix_sort(): # 静态链表初始化, 下标 0 为头结点 for i = 0 to N-1: next[i] = i+1 next[N] = 0 # 从低位至高位进行排序 for i = 0 to KEYS-1: distribute(i) collect(i)
对一个数组 L[n]
进行排序,
假定我们已经知道第 i
个元素应该移动到 sigma(i)
的位置.
那么最少交换次数是多少? 具体方案是怎样的?
地址排序 原记录表 L
占空间较大时,
可以只排序指向记录的指针表 adr
,
再通过地址排序调整原记录的位置. 下面假设 adr
给出了 L
中元素的次序, 即
L[adr[i]], i = 0..L.length-1
是
L
的一个单调增序列.
算法实际上将各个静态循环链表逆向调整了一个位置.
rearrange(L, adr): for i = 0 to L.length-1: if adr[i] != i: # 没有归位, 需要调整 j = i tmp = L[j] k = adr[j] do: L[j] = L[k] adr[j] = j # 作标记, 表示已经归位 j = k k = adr[k] while k != i L[j] = tmp adr[j] = j
败者树
int loser[N] # loser[1] 是根结点, loser[0] 是冠军 int leaf[N+1] # leaf[N] 作特殊用途, 见 init() # 从 leaf[i] 出发向上调整, 败者留在结点中, 胜者向上继续比赛 adjust(i): p = (N+i+1) >> 1 # 求父结点下标 while p: if leaf[i] > leaf[loser[p]]: swap(i, loser[p]) p >>= 1 loser[0] = i # 选出冠军 init(): # 在虚拟结点中存储"负无穷大", 让所有的 loser 指针指向它 leaf[N] = -INF for i = 0 to N-1: loser[i] = N # 从后往前依次读入记录, 并调整叶子结点 for i = N-1 downto 0: input(leaf[i]) # 读取输入, 该路已无数据时, 得到 INF adjust(i) # 调整 N 次后, 产生第一个冠军 # 利用败者树将 leaf[0..N-1] 的记录进行 N 路归并 N_merge(): init() # 每输出一个冠军, 就读入新的记录代替它, 直到没有新数据 i = loser[0] while leaf[i] != INF: output(leaf[i]) input(leaf[i]) adjust(i) i = loser[0]
败者树构建过程示意. 下面圆括号代表 loser 结点, 方括号代表 leaf 结点. 从上而下, 从左而右, loser 结点和 leaf 结点的下标分别是 0, 1, 2, ... 为直观起见, loser 结点上写的是值而不是指针. ( ) ( ) ( ) ( ) | | | | ( ) ( ) ( ) (3) / \ / \ / \ / \ ( ) ( ) (2) ( ) (2) (9) (2) (9) / \ / \ / \ / \ / \ / \ / \ / \ (7) [ ][ ][ ] (7) [ ][ ][ ] (7) [ ][ ][9] (7) [ ][3][9] / \ / \ / \ / \ [ ][7] [2][7] [2][7] [2][7] (2) | (3) / \ (5) (9) / \ / \ (7) [5][3][9] / \ [2][7]
置换-选择排序
int loser[N] # 败者树 int key[N] # 关键字, 放在败者树的叶结点 int tag[N] # 归并段号 int cur_tag = 1 # 当前段号 int max_tag = 1 # 最大段号 lose(i, j): return tag[i] > tag[j]\ or (tag[i] == tag[j] and key[i] > key[j]) adjust(i): p = (N+i+1) >> 1 while p: if lose(i, loser[p]): swap(i, loser[p]) p >>= 1 loser[0] = i init(): for i = 0 to N-1: tag[i] = key[i] = loser[i] = 0 for i = N-1 downto 0: if input(key[i]): tag[i] = cur_tag else: tag[i] = max_tag + 1 adjust(i) # 求下一归并段 next_segment(): w = loser[0] # w = winner while tag[w] == cur_tag: # 选得的冠军属于当前段时 key_to_put = key[w] output(key_to_put) if !input(key[w]): # 输入文件结束, 虚设段号为 max_tag + 1 tag[w] = max_tag + 1 elif key[w] < key_to_put: # 读入数据较小, 应属于下一段 max_tag = cur_tag + 1 tag[w] = max_tag adjust(w) w = loser[0] output(SEGMENT_DILIMITER) # 将段结束标志写入输出文件 cur_tag = tag[w] replace_select_sort(): init() while cur_tag <= max_tag: next_segment()