分解(Divide):如输入数据 S 规模较小则直接解决;否则,将其分成两个或多个互斥子集 S1, S2, …
解决子问题(Conquer):递归解决与子集相关的子问题
合并(Combine):将子问题 S1, S2, … 的解合并为 S 的解
1.2 归并排序
**归并排序(merge-sort)**基于分治法思想。在有 n 个元素的序列 S 上执行归并排序的过程如下:
分解(Divide):当序列 S 长度大于 1 时,将其分为两个长度大致为 n/2 的子序列 S1 和 S2
解决子问题(Conquer):递归地对 S1 和 S2 进行排序
合并(Combine):将排好序的 S1 和 S2 合并为一个有序的序列
归并排序伪代码:
1 2 3 4 5 6 7 8 9
Algorithm mergeSort(S) Input sequence S with n elements Output sequence S sorted if S.size() > 1 (S1, S2) = partition(S, n/2) # 拆分 mergeSort(S1) # 递归排序 mergeSort(S2) S = merge(S1, S2) # 合并
在归并排序的最后一步(合并)中,需要将两个有序序列 A 和 B 合并为一个有序序列 S 这一合并操作的时间复杂度为 O(n) 。
合并操作的伪代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Algorithm merge(A, B) Input sequences A and B with n/2 elements each Output sorted sequence of A + B
S = empty sequence whilenot A.isEmpty() andnot B.isEmpty() if A.first().element() < B.first().element() S.addLast(A.remove(A.first())) # 从小到大插入 else S.addLast(B.remove(B.first())) whilenot A.isEmpty() # 将剩余部分插入 S.addLast(A.remove(A.first())) whilenot B.isEmpty() S.addLast(B.remove(B.first())) return S
defmerge(S1: list, S2: list, S: list) -> None: """ 合并数组/序列 S1 S2 返回新的数组/序列 :param S1: 顺序序列 S1 :param S2: 顺序序列 S2 :param S: 用于存储最终合并的结果,按照从小到大排序的序列 :return: None """ i = j = 0 while i + j < len(S): if j == len(S2) or (i < len(S1) and S1[i] < S2[j]): S[i + j] = S1[i] # copy i th element of S1 as next item of S i += 1 else: S[i + j] = S2[j] # copy j th element of S2 as next item of S j += 1
defmerge_sort(S: list) -> None: """ 对序列 S 进行归并排序,直接修改原序列 :param S: 等待排序的序列 :return: None """ n = len(S) if n < 2: return# list is already sorted
# divide mid = n // 2 S1 = S[0:mid] # copy of first half S2 = S[mid:n] # copy of second half
# conquer (with recursion) merge_sort(S1) # sort copy of first half merge_sort(S2) # sort copy of second half
# merge results merge(S1, S2, S) # merge sorted halves back into S
whilenot S1.is_empty(): # move remaining elements of S1 to S S.enqueue(S1.dequeue()) whilenot S2.is_empty(): # move remaining elements of 52 to S S.enqueue(S2.dequeue())
defmerge_sort(S: LinkedQueue): """对链表使用归并排序""" n = len(S) if n < 2: return# list is already sorted
# divide S1 = LinkedQueue() S2 = LinkedQueue() whilelen(S1) < n // 2: # move the first n//2 elements to S1 S1.enqueue(S.dequeue()) whilenot S.is_empty(): # move the rest to S2 S2.enqueue(S.dequeue())
# conquer (with recursion) merge_sort(S1) # sort first half merge_sort(S2) # sort second half
# merge results merge(S1, S2, S) # merge sorted halves back into S
=============== Original List =============== [85, 24, 63, 45, 17, 31, 96, 50] =============== After Sorted =============== [17, 24, 31, 45, 50, 63, 85, 96]
1.5 算法分析
归并排序树的高度 h = O(log n) 。在深度为 i 的一层所需的时间为 O(n) 。我们对长度为 n/2i 的 2i 个序列进行合并,并进行 2i+1 次递归调用。因此,归并排序的运行时间为 O(n log n) 。
2 快速排序
2.1 快速排序
**快速排序(quick-sort)**是一种基于分治法的随机排序算法,在序列 S 上其执行过程如下:
分解(Divide):随机选取一个元素 x,此元素被称为基准值(pivot),将序列分割为三部分:L 存储小于 x 的元素; E 存储等于 x 的元素; G 存储大于 x 的元素
解决子问题(Conquer):递归地对 L 和 G 排序
合并(Combine):合并 L 、 E 和 G
2.2 分割
我们采用如下方式对一个序列 S 进行分割:
我们将 S 中的每一个元素 y 移出序列
根据 y 与基准值 x 的比较结果,将其插入 L 、 E 或 G
每一次插入或删除操作是在序列的尾或头进行的,因此其运行时间复杂度为 O(1)
由此可得,快速排序中每次分割操作的时间复杂度为 O(n)
伪代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Algorithm partition(S, p) Input sequence S, position p of pivot Output subsequences L, E, G of the elements of S less than, equal to, or greater than the pivot, resp.
L, E, G <- empty sequences x = S.remove(p) whilenot S.isEmpty() y = S.remove(S.first()) if y < x L.addLast(y) elseif y = x E.addLast(y) else { y > x } G.addLast(y) return L, E, G
defquick_sort(S: LinkedQueue) -> None: """基于链表的快速排序""" n = len(S) if n < 2: return# list is already sorted
# divide p = S.first() # 取第一个值为划分基准 L = LinkedQueue() E = LinkedQueue() G = LinkedQueue()
# divide S into L, E, and G whilenot S.is_empty(): if S.first() < p: L.enqueue(S.dequeue()) elif p < S.first(): G.enqueue(S.dequeue()) else: # S.first() must equal pivot 等于基准值 E.enqueue(S.dequeue())
# conquer (with recursion) 递归排序 quick_sort(L) # sort elements less than p quick_sort(G) # sort elements greater than p
definplace_quick_sort(S: list, a: int, b: int) -> None: """就地快速排序: 序列 S 从 S[a] 到 S[b]""" if a >= b: return
pivot = S[b] # 取 S[b] 为基准 left = a # 从左向右 right = b - 1# 从右向左
while left <= right: # 直到相交 # scan until reaching value equal or larger than pivot (or right marker) while left <= right and S[left] < pivot: left += 1
# scan until reaching value equal or smaller than pivot (or left marker) while left <= right and pivot < S[right]: right -= 1
if left <= right: # 逆序, 则交换 S[left], S[right] = S[right], S[left] # swap values left, right = left + 1, right - 1# 继续移动
# put pivot into its final place (currently marked by left index) S[left], S[b] = S[b], S[left] # 将基准值放到中间
# make recursive calls 递归调用 inplace_quick_sort(S, a, left - 1) inplace_quick_sort(S, left + 1, b)
测试:
1 2 3 4 5 6 7 8 9
if __name__ == '__main__': S = [85, 24, 63, 45, 17, 31, 96, 50]
Algorithm bucketSort(S): Input: Sequence S of entries with integer keys in the range [0, N − 1] Output: Sequence S sortedin nondecreasing order of the keys let B be an array of N sequences, each of which is initially empty
# 阶段 1 : 将 (k, v) 插入 B[k] for each entry e in S do k = the key of e remove e from S and insert e at the end of bucket B[k] # 阶段 2 : 按顺序返回 B[i] for i = 0 to N − 1 do for each entry e in B[i] do remove e from B[i] and insert e at the end of S
桶排序对键的要求:
键被用作插入辅助数组的索引,因此对键的类型有所限制
不需要使用额外的比较器(comparator)进行比较操作
3.3.2 稳定排序
**稳定排序(stable sorting):**如果两个键相同的元素在排序前的顺序与排序后的顺序一致,则称此排序方法为稳定的。例如上图例子中 (3, a) (3, b) 的顺序没有改变。
归并排序、桶排序是稳定排序算法
3.3.3 字典序排序
一个 d 元组为一个由 d 个键构成的序列 (k1,k2,⋯,kd) ,其中键 ki 被称为此 d 元组的第 i 个维度。例如,三维空间中一个点的笛卡尔坐标为一个三元组 (x, y, z)
Algorithm lexicographicSort(S) Input sequence S of d-tuples Output sequence S sortedin lexicographic order # 从后向前使用稳定排序 for i = d down to 1 stableSort(S, Ci)
Algorithm radixSort(S, N) Input sequence S of d-tuples such that (0, ..., 0) < (x1, ..., xd) and (x1, ..., xd) < (N − 1, …, N − 1) for each tuple (x1, ..., xd) in S Output sequence S sortedin lexicographic order
# 从后向前使用桶排序 for i = d down to 1 bucketSort(Si, N)
4 排序算法的比较
4.1 排序算法的比较
下面我们比较一些常见的排序算法:
**插入排序:**如果情况好的话,插入排序的运行时间是 O(n+m) ,其中 m 是逆序的数量(即无序元素对数目)。因此,插入排序是一种进行小序列排序的优秀算法(比如,少于50个元素),因为插入排序是很简单的程序,而且小序列最多只有几个逆序。但是插入排序在最坏情况仍然会达到 O(n2) 。
defquick_select(S, k): """返回 S 中第 k 小的数""" iflen(S) == 1: return S[0]
pivot = random.choice(S) # 随机选取基准 # 分为 3 个子序列 L = [x for x in S if x < pivot] E = [x for x in S if x == pivot] G = [x for x in S if pivot < x]
if k <= len(L): return quick_select(L, k) # k th smallest lies in L elif k <= len(L) + len(E): return pivot # k th smallest equal to pivot else: j = k - len(L) - len(E) # new selection parameter return quick_select(G, j) # k th smallest is jth in G
测试:
1 2 3 4 5 6 7 8 9 10
if __name__ == '__main__': S = [85, 24, 63, 45, 17, 31, 96, 50]