分治
阅读信息
432 字 4 分钟 本页总访问量 加载中...
提醒
和 Noflower/算法设计分析/分治 高度重合,点击 此处 可移步。
最近点对问题
在平面上给定 \( n \) 个点,希望找出其中距离最近的两个点。
输入:平面上 \( n \) 个点 \( P = \{p_1, p_2, \dots, p_n\} \),每个点 \( p_i = (x_i, y_i) \)。
输出:一对点 \( (p_i, p_j) \)(\( i \ne j \)),使得它们之间的欧几里得距离最小。
分治法步骤:
- 将点集分别按 x 坐标和 y 坐标排序,得到数组 \( P_x \) 和 \( P_y \)。
- 用一条垂直线将点集分为左右两半,每边约 \( n/2 \) 个点,递归求解左半的最近距离 \( d_L \),右半的最近距离 \( d_R \)。令 \( d = \min(d_L, d_R) \)。
- 合并时要考虑跨越分割线的点对,只需取半宽为 \(d\) 的带状区域(strip)即可。对 strip 中的点按 y 坐标排序,检查邻近的点,计算这些候选点对的距离,更新最小距离。

理解:将 strip 划分为边长的 \(d/2\) 的方格,将要考虑的点放在一条底边。希望距离小于 \(d\),故一个方格以外的点不用考虑;而因为两边的最小距离都小于 \(d\),一个方格内最多只有一个点。所以对于 strip 中的点,只需要检查相邻的 7 个方格,最多有 7 个邻居点,为常数时间。
实际操作中,因为提前将点集按 y 坐标排序,只需比较后面至多 7 个点。
代码示例 分治法最近点对
时间复杂度:主定理
一般,分治 TC 的递推式为:
认为 \(f(n)=n^c\),得到主定理(Master Theorem)表达式:
主定理推导
第一步:展开递推式(可选,用于理解)
我们也可以通过递归树或展开来直观理解:
当递归到底层时,\( \frac{n}{b^k} = 1 \Rightarrow k = \log_b n \)。
所以总时间复杂度为:
注意到 \( a^{\log_b n} = n^{\log_b a} \),因此:
现在关键看比值 \( \frac{a}{b^c} \):
第二步:分情况讨论(即主定理的三种情形)
令 \( \alpha = \log_b a \),即 \( a = b^\alpha \)。比较 \( \alpha \) 与 \( c \):
情况 1:\( c < \log_b a \)(即 \( f(n) = n^c \) 多项式小于 \( n^{\log_b a} \))
此时 \( \frac{a}{b^c} = b^{\log_b a - c} > 1 \),几何级数主导项是最后一项:
所以:
结论:若 \( f(n) = O(n^c) \) 且 \( c < \log_b a \),则
情况 2:\( c = \log_b a \)
此时 \( \frac{a}{b^c} = 1 \),几何级数变成:
所以:
结论:若 \( f(n) = \Theta(n^{\log_b a}) \),则
情况 3:\( c > \log_b a \)
此时 \( \frac{a}{b^c} < 1 \),几何级数收敛到常数:
所以:
但注意:主定理的第三种情况还需要满足正则条件(regularity condition): 存在 \( \varepsilon > 0 \),使得 \( a f(n/b) \leq k f(n) \) 对某个 \( k < 1 \) 成立(通常对多项式 \( f(n) = n^c \) 自动满足)。
结论:若 \( f(n) = \Omega(n^c) \) 且 \( c > \log_b a \),且满足正则条件,则
总结(主定理,当 \( f(n) = n^c \) 时)
设 \( T(n) = aT(n/b) + n^c \),其中 \( a \geq 1, b > 1, c \geq 0 \),则:
- 若 \( c < \log_b a \),则 \( T(n) = \Theta(n^{\log_b a}) \)
- 若 \( c = \log_b a \),则 \( T(n) = \Theta(n^c \log n) \)
- 若 \( c > \log_b a \),则 \( T(n) = \Theta(n^c) \)
主定理不适用的情况:
- \(a\) 不是常数
- \(a<1\)
- \(f(n)\) 不是 \(\Theta(n^c)\)
扩展主定理:
它进一步允许 \(f(n)\) 在渐进意义上带有对数幂项,即:
其中 \(c\ge0,\ k\ge 0\)。
则递推式:
的解为:
扩展主定理推导
设 \(f(n)=\Theta(n^{c}(\log n)^k)\),且 \(aT(n/b)\) 把问题划分为 \(a\) 个规模 \(n/b\) 的子问题。
第 1 层(根),代价:\(f(n)\)。
第 2 层,个子问题规模 \(n/b\),共有 \(a\) 个,总代价:
第 \(i\) 层,共有 \(a^i\) 个子问题,规模 \(n/b^i\),总代价:
层数 \(L=\log_b n\)。
分三种情况
(1) 当 \(a/b^c < 1\)(即 \(c>\log_b a\))
上层代价逐层衰减,主导项在根层。
(2) 当 \(a/b^c > 1\)(即 \(c<\log_b a\))
每层代价增长,最后一层最大:
(3) 当 \(a/b^c = 1\)(即 \(c=\log_b a\))
每层代价大约相等:
层数 \(\log*b n\),把这些相加:
这就是扩展主定理第 2 种情况的来源。
二进制整数乘法
将整数按二进制位划分,拆成高位和低位两部分:
于是:
若递归地计算四个 \( \tfrac{n}{2} \)-位数的乘积((ac, ad, bc, bd)),得到递推:\(T(n) = 4T(n/2) + \Theta(n)\),解得 \( T(n) = \Theta(n^2) \),并没有改进。
Karatsuba 改进:
因此只需计算 \(ac,\ bd,\ (a+b)(c+d)\) 三个乘法。
\(T(n) = 3T(n/2) + \Theta(n)= \Theta(n^{\log_2 3}) = O(n^{1.585})\),显著优于\( T(n) = \Theta(n^2) \) 的方法。
矩阵乘法
对两个 \( n \times n \) 矩阵 \( A, B \):
传统方法需要 \( n^3 \) 次标量乘法和 \( n^3 - n^2 \) 次加法,时间复杂度为 \(\Theta(n^3)\)。
将矩阵分为四个 \( \frac{n}{2} \times \frac{n}{2} \) 的子块:
则:
这样仍需要 8 次乘法,时间复杂度上无改进。
Strassen 注意到:通过巧妙的线性组合只用 7 次矩阵乘法(外加 18 次加减法),减少一次乘法:
然后组合成:
递推式:
主定理解得:
这是第一个低于 \( n^3 \) 的矩阵乘法算法。
采用更巧妙的分割方式(可利用机器学习),能得到更低的时间复杂度。但在实际应用中仍使用 \(O(n^3)\) 的方法。
快速傅里叶变换 FFT
一个 n 次的复系数单变量多项式恰好有 n 个复根。一个 n-1 次的单变量多项式 A(x)由其在 n 个不同 x 值处的取值唯一确定。
表示 n-1 次多项式:n 个系数,或 n 个点的取值。使用 point-value 表示能快速表示多项式乘法,但判断单个点是否在曲线上的代价更大。
多项式乘法:系数表示 - FFT -> point-value 表示 -> point-value 相乘 - inverse FFT -> 系数表示 FFT 在时域和频域间快速转化
将 n 次多项式按奇偶分为两个 n/2 次多项式,互为相反数的点的取值可转化为 n/2 次多项式取值的线性组合。
递归计算奇数和偶数部分,时间复杂度为 \(O(n\log n)\)。
其他
矩阵幂求斐波那契
斐波那契数列中,\(F_1=1\),\(F_2=1\)。
定义矩阵 \(M\):
则:
故可用矩阵的幂 \(M^n\) 求 \(F_n\)。
用快速幂方法求幂的时间复杂度为 \(O(\log n)\),故计算第 \(n^2\)、第 \(n^3\) 项斐波那契数列的时间复杂度均为 \(O(\log (n^k))=O(\log n)\)。
两个数组找第 k 小值
给定两个有序数组 A、B,长度分别为 m、n。给定 k(\(k<min\{m,n\}\)),要在两数组的合并数组中找第 k 小的值,求最小时间复杂度?
最终情况为将 A、B 分别划分为两部分,且两者左边部分的最大值小于两者右边部分的最小值,左边部分个数和为 k。只需要找到两个数组中这个划分的位置。
假设 A 中划分位置 i 初始为 k,B 中划分位置 j 初始为 0。每次二分调整,保证 i+j=k,直到满足上述条件。时间复杂度约为 \(O(\log k)\)。
代码示例 返回两有序数组合并后的中点
来自LeetCode 4. Median of Two Sorted Arrays.
合并有序数组的时间复杂度
给定 k 个有序数组,k 个数组中元素总数为 n。求合并这 k 个数组的最小时间复杂度?
每次在 k 个数组的头部选出最小的元素,即将头部元素都放到最小堆中,取堆顶。时间复杂度为 \(O(\log k)\)。
一共需要取 n 个,故总时间复杂度为 \(O(n\log k)\)。
几种时间复杂度