已发布·持续改进
Algorithm
分治法把问题拆成更小的子问题,递归求解后再合并结果,是归并排序、快速幂和快速选择等算法的基础。
分治法(divide and conquer)是一种算法设计方法:把问题拆成规模更小的同类子问题(分解),递归地求解这些子问题(解决),再用它们的结果构造原问题的答案(合并)。归并排序、二分查找、快速幂、快速选择、Karatsuba 乘法和最近点对算法都遵循这一模式。
分治法之所以重要,是因为它常常能把 O(n^2) 的暴力解法降到 O(n log n) 甚至 O(log n)。像统计逆序对那样,在合并阶段高效计算跨越分界线的信息,是面试和算法竞赛中的常见思路;同样的思想也用在排序库、大整数乘法、FFT 以及密码学中的模幂运算等实际系统里。
学习时,先手动跟踪归并排序,熟悉分解、解决、合并的流程,再练习写出 T(n) = aT(n/b) + f(n) 这样的递推式,并用主定理求出时间复杂度。接着亲手实现逆序对计数、快速幂和快速选择,并学会判断:当子问题相互重叠时,应改用动态规划。
分解输入、递归求解各部分、合并答案。基本情况让递归停止,合并的代价通常决定整体运行时间。
把运行时间写成 T(n) = aT(n/b) + f(n),再将 f(n) 与 n^(log_b a) 比较,就能直接得到归并排序 O(n log n) 这样的结果。
在合并阶段用线性时间处理横跨两半的情况,例如逆序对计数和最大子数组和,是分治法的核心思路。
像二分查找、快速幂和快速选择那样只保留一个子问题,就能得到 O(log n) 或期望 O(n) 的算法。
sort_count 用归并排序对列表排序,每当右半部分的元素先被取出时,就加上左侧仍在等待的元素个数,从而在 O(n log n) 内统计逆序对。power 是快速幂:不断把指数减半并平方,只需 O(log e) 次乘法。运行 python divide_and_conquer.py 会输出排序后的列表、逆序对数 14,以及 3^200 除以 1,000,000,007 的余数。
divide_and_conquer.py
def sort_count(a):
"""Return (sorted list, number of inversions) using merge sort."""
if len(a) <= 1:
return list(a), 0
mid = len(a) // 2
left, x = sort_count(a[:mid])
right, y = sort_count(a[mid:])
merged, i, j, cross = [], 0, 0, 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
merged.append(left[i])
i += 1
else:
merged.append(right[j])
j += 1
cross += len(left) - i # right[j] is smaller than every left[i:]
merged += left[i:] + right[j:]
return merged, x + y + cross
def power(base, exp, mod):
"""base ** exp % mod with O(log exp) multiplications."""
if exp == 0:
return 1 % mod
half = power(base, exp // 2, mod)
result = half * half % mod
return result * base % mod if exp % 2 else result
if __name__ == "__main__":
print(sort_count([5, 2, 4, 7, 1, 3, 2, 6])) # ([1, 2, 2, 3, 4, 5, 6, 7], 14)
print(power(3, 200, 1_000_000_007)) # 136318165
python divide_and_conquer.py共六章,带你从安装一步步了解 分治法 的核心概念。
在这里提问、分享经验,交流关于 分治法 的看法。
还没有讨论。来发起第一个吧。
0 条评论
登录 · 登录后即可发表评论。
来发表第一条评论吧。