函数的增长
3 / 11
堆排序
自在学
分类课程AI导师创意工坊价格
分类课程AI导师创意工坊价格
编程算法入门分治

分治法

分治法(Divide-and-Conquer)是算法设计中的核心范式之一,通过将复杂问题分解为结构相似的子问题,递归求解后合并结果,从而高效地解决原问题。 在算法入门部分,我们已通过归并排序初步了解了分治法的基本思想。本章将系统阐述分治法的理论框架,并通过最大子数组问题和 Strassen 矩阵乘法两个经典案例,深入探讨分治法在算法设计中的应用及其复杂度分析。

分治法


分治法的核心思想

分治法采用递归策略,其执行过程可形式化为三个基本阶段:

分解(Divide):将原问题 PPP 分解为 kkk 个规模更小的子问题 P1,P2,…,PkP_1, P_2, \ldots, P_kP1​,P2​,…,Pk​,其中每个子问题 PiP_iPi​ 的结构与原问题 PPP 相同,但规模更小。

解决(Conquer):递归地求解各子问题。若子问题规模足够小,可直接求解(称为基本情况,Base Case);否则继续递归分解(称为递归情形,Recursive Case)。

合并(Combine):将各子问题的解 S1,S2,…,SkS_1, S_2, \ldots, S_kS1​,S2​,…,Sk​ 合并,构造原问题 PPP 的解 SSS。

分治法并非适用于所有问题。一个问题能够有效应用分治法,需满足以下必要条件:

  • 可分解性:问题可分解为规模更小的同类型子问题,且子问题规模严格小于原问题规模
  • 子问题独立性:子问题之间相互独立,求解一个子问题不影响其他子问题的求解过程
  • 合并可行性:子问题的解可在多项式时间内合并为原问题的解
  • 规模递减性:子问题规模必须严格递减,确保递归过程在有限步内终止

分治法的优势与局限

优势局限
将复杂问题转化为简单子问题,降低求解难度递归调用产生的函数调用栈开销可能抵消分治带来的性能收益
天然支持并行化处理,子问题可独立并行求解合并步骤可能成为算法性能瓶颈,影响整体效率
算法结构清晰,易于理解、实现和维护对于某些问题,分治法的时间复杂度可能不如其他算法设计方法

最大子数组问题

最大子数组问题(Maximum Subarray Problem)是算法设计中的经典问题。给定一个包含 nnn 个元素的数组 A[1..n]A[1..n]A[1..n],其中每个元素 A[i]A[i]A[i] 表示第 iii 个元素的值,目标是找到数组 AAA 中元素和最大的连续子数组,并返回该子数组的起始位置、结束位置及其元素和。

形式化地,我们需要找到索引 iii 和 jjj(1≤i≤j≤n1 \leq i \leq j \leq n1≤i≤j≤n),使得 ∑k=ijA[k]\sum_{k=i}^{j} A[k]∑k=ij​A[k] 达到最大值。

例如,对于数组 A=[13,−3,−25,20,−3,−16,−23,18,20,−7,12,−5,−22,15,−4,7]A = [13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7]A=[13,−3,−25,20,−3,−16,−23,18,20,−7,12,−5,−22,15,−4,7],最大子数组为 A[8..11]=[18,20,−7,12]A[8..11] = [18, 20, -7, 12]A[8..11]=[18,20,−7,12],其元素和为 434343。

暴力求解方法需要检查所有可能的子数组,共有 (n+12)=n(n+1)2\binom{n+1}{2} = \frac{n(n+1)}{2}(2n+1​)=2n(n+1)​ 个子数组,时间复杂度为 Θ(n2)\Theta(n^2)Θ(n2)。采用分治法可将时间复杂度降低至 Θ(nlg⁡n)\Theta(n \lg n)Θ(nlgn)。

分治求解策略

对于数组 A[low..high]A[\text{low}..\text{high}]A[low..high],设其中点索引为 mid=⌊(low+high)/2⌋\text{mid} = \lfloor(\text{low} + \text{high})/2\rfloormid=⌊(low+high)/2⌋。最大子数组的位置存在三种可能情形:

  1. 完全位于左子数组 A[low..mid]A[\text{low}..\text{mid}]A[low..mid] 中
  2. 完全位于右子数组 A[mid+1..high]A[\text{mid}+1..\text{high}]A[mid+1..high] 中
  3. 跨越中点 mid\text{mid}mid,即包含 A[mid]A[\text{mid}]A[mid] 和 A[mid+1]A[\text{mid}+1]A[mid+1]

前两种情形可通过递归求解。第三种情形"跨越中点"是合并步骤的关键。跨越中点的最大子数组必然由两部分组成:左半部分 A[i..mid]A[i..\text{mid}]A[i..mid] 和右半部分 A[mid+1..j]A[\text{mid}+1..j]A[mid+1..j],其中 i∈[low,mid]i \in [\text{low}, \text{mid}]i∈[low,mid],j∈[mid+1,high]j \in [\text{mid}+1, \text{high}]j∈[mid+1,high]。

我们可在 Θ(high−low)\Theta(\text{high} - \text{low})Θ(high−low) 时间内找到这样的索引对 (i,j)(i, j)(i,j),方法是从 mid\text{mid}mid 开始分别向左、向右扫描,寻找和最大的子数组。

以下是寻找跨越中点的最大子数组的算法:

|
FIND-MAX-CROSSING-SUBARRAY(A, low, mid, high) 1 left-sum = -∞ // 初始化左半部分最大和 2 sum = 0 // 当前累加和 3 for i = mid downto low // 从mid向左扫描 4 sum = sum + A[i] // 累加当前元素 5 if sum > left-sum // 更新最大和 6 left-sum = sum 7 max-left = i // 记录左边界 8 right-sum = -∞ // 初始化右半部分最大和 9 sum = 0 // 重置累加和 10 for j = mid + 1 to high // 从mid+1向右扫描 11 sum = sum + A[j] // 累加当前元素 12 if sum > right-sum // 更新最大和 13 right-sum = sum 14 max-right = j // 记录右边界 15 return (max-left, max-right, left-sum + right-sum) // 返回跨越中点的最大子数组

基于上述辅助函数,可构造完整的分治算法:

|
FIND-MAXIMUM-SUBARRAY(A, low, high) 1 if high == low // 基本情况:只有一个元素 2 return (low, high, A[low]) // 返回该元素本身 3 else 4 mid = floor((low + high) / 2) // 计算中点 5 (left-low, left-high, left-sum) = FIND-MAXIMUM-SUBARRAY(A, low, mid) // 递归求解左子数组 6 (right-low, right-high, right-sum) = FIND-MAXIMUM-SUBARRAY(A, mid + 1, high) // 递归求解右子数组 7 (cross-low, cross-high, cross-sum) = FIND-MAX-CROSSING-SUBARRAY(A, low, mid, high) // 求解跨越中点的子数组 8 if left-sum >= right-sum and left-sum >= cross-sum 9 return (left-low, left-high, left-sum) // 左子数组的解最优 10 elseif right-sum >= left-sum and right-sum >= cross-sum 11 return (right-low, right-high, right-sum) // 右子数组的解最优 12 else 13 return (cross-low, cross-high, cross-sum) // 跨越中点的解最优

时间复杂度分析:设 T(n)T(n)T(n) 表示算法处理长度为 nnn 的数组的运行时间。算法将问题分解为两个规模为 n/2n/2n/2 的子问题,合并步骤(寻找跨越中点的最大子数组)需要 Θ(n)\Theta(n)Θ(n) 时间。因此,递推关系为:

T(n)=2T(n/2)+Θ(n)T(n) = 2T(n/2) + \Theta(n)T(n)=2T(n/2)+Θ(n)

根据主方法(Master Method),该递推式的解为 T(n)=Θ(nlg⁡n)T(n) = \Theta(n \lg n)T(n)=Θ(nlgn),优于暴力解法的 Θ(n2)\Theta(n^2)Θ(n2)。

线性时间解法:Kadane 算法

尽管分治法将最大子数组问题的时间复杂度从 Θ(n2)\Theta(n^2)Θ(n2) 降低至 Θ(nlg⁡n)\Theta(n \lg n)Θ(nlgn),但存在更优的线性时间算法——Kadane 算法。

Kadane 算法的核心思想基于动态规划:维护两个变量,max_ending_here 表示以当前位置结尾的最大子数组和,max_so_far 表示迄今为止找到的最大子数组和。算法的关键洞察是:若以位置 i−1i-1i−1 结尾的最大子数组和为负,则将其丢弃,从位置 iii 重新开始计算,因为负的子数组和不可能对后续结果产生正贡献。

|
KADANE-MAXIMUM-SUBARRAY(A) 1 max-so-far = A[1] // 全局最大和,初始化为第一个元素 2 max-ending-here = A[1] // 以当前位置结尾的最大和 3 start = 1 // 最大子数组起始位置 4 end = 1 // 最大子数组结束位置 5 temp-start = 1 // 临时起始位置 6 for i = 2 to n // 遍历数组 7 if max-ending-here + A[i] > A[i] // 扩展当前子数组更优 8 max-ending-here = max-ending-here + A[i] 9 else // 从当前位置重新开始 10 max-ending-here = A[i] 11 temp-start = i // 更新临时起始位置 12 if max-ending-here > max-so-far // 更新全局最大值 13 max-so-far = max-ending-here 14 start = temp-start // 更新起始位置 15 end = i // 更新结束位置 16 return (start, end, max-so-far) // 返回最大子数组信息

复杂度分析:Kadane 算法的时间复杂度为 Θ(n)\Theta(n)Θ(n),空间复杂度为 Θ(1)\Theta(1)Θ(1),是解决最大子数组问题的最优算法。


Strassen 矩阵乘法

矩阵乘法是线性代数中的基本运算,在科学计算、机器学习、图像处理等领域有广泛应用。给定两个 n×nn \times nn×n 矩阵 AAA 和 BBB,其乘积 C=A⋅BC = A \cdot BC=A⋅B 是一个 n×nn \times nn×n 矩阵,其中元素 cijc_{ij}cij​ 定义为:

cij=∑k=1naikbkjc_{ij} = \sum_{k=1}^{n} a_{ik} b_{kj}cij​=∑k=1n​aik​bkj​

标准矩阵乘法算法需要计算 n2n^2n2 个元素,每个元素需要 nnn 次乘法和 n−1n-1n−1 次加法,因此时间复杂度为 Θ(n3)\Theta(n^3)Θ(n3)。

朴素的分治解法

采用分治法,将 n×nn \times nn×n 矩阵分解为 4 个 (n/2)×(n/2)(n/2) \times (n/2)(n/2)×(n/2) 的子矩阵。设:

C=(C11C12C21C22),A=(A11A12A21A22),B=(B11B12B21B22)C = \begin{pmatrix} C_{11} & C_{12} \\ C_{21} & C_{22} \end{pmatrix}, \quad A = \begin{pmatrix} A_{11} & A_{12} \\ A_{21} & A_{22} \end{pmatrix}, \quad B = \begin{pmatrix} B_{11} & B_{12} \\ B_{21} & B_{22} \end{pmatrix}C=(C11​C21​​C12​C22​​),A=(A11​A21​​A12​A22​​),B=(B11​B21​​B12​B22​​)

则矩阵乘法可表示为:

(C11C12C21C22)=(A11A12A21A22)(B11B12B21B22)\begin{pmatrix} C_{11} & C_{12} \\ C_{21} & C_{22} \end{pmatrix} = \begin{pmatrix} A_{11} & A_{12} \\ A_{21} & A_{22} \end{pmatrix} \begin{pmatrix} B_{11} & B_{12} \\ B_{21} & B_{22} \end{pmatrix}(C11​C21​​C12​C22​​)=(A11​A21​​A12​A22​​)(B11​B21​​B12​B22​​)

展开后得到:

C11=A11B11+A12B21C12=A11B12+A12B22C21=A21B11+A22B21C22=A21B12+A22B22\begin{align} C_{11} &= A_{11}B_{11} + A_{12}B_{21} \\ C_{12} &= A_{11}B_{12} + A_{12}B_{22} \\ C_{21} &= A_{21}B_{11} + A_{22}B_{21} \\ C_{22} &= A_{21}B_{12} + A_{22}B_{22} \end{align}C11​C12​C21​C22​​=A11​B11​+A12​B21​=A11​B12​+A12​B22​=A21​B11​+A22​B21​=A21​B12​+A22​B22​​​

该分治策略需要 8 次 (n/2)×(n/2)(n/2) \times (n/2)(n/2)×(n/2) 规模的矩阵乘法和 4 次矩阵加法。矩阵加法的时间复杂度为 Θ(n2)\Theta(n^2)Θ(n2),因此递推关系为:

T(n)=8T(n/2)+Θ(n2)T(n) = 8T(n/2) + \Theta(n^2)T(n)=8T(n/2)+Θ(n2)

根据主方法,a=8a = 8a=8,b=2b = 2b=2,f(n)=Θ(n2)f(n) = \Theta(n^2)f(n)=Θ(n2)。计算 log⁡ba=log⁡28=3\log_b a = \log_2 8 = 3logb​a=log2​8=3,因此 nlog⁡ba=n3n^{\log_b a} = n^3nlogb​a=n3。由于 f(n)=Θ(n2)=O(n3−ε)f(n) = \Theta(n^2) = O(n^{3-\varepsilon})f(n)=Θ(n2)=O(n3−ε)(其中 ε=1>0\varepsilon = 1 > 0ε=1>0),属于主方法的情况一,因此 T(n)=Θ(n3)T(n) = \Theta(n^3)T(n)=Θ(n3)。这表明朴素的分治策略并未带来性能提升。

Strassen 算法

1969 年,Volker Strassen 提出了一种突破性的矩阵乘法算法,通过巧妙的代数变换,将子问题的矩阵乘法次数从 8 次减少至 7 次,代价是增加了加法和减法运算次数(共 18 次)。其递推关系为:

T(n)=7T(n/2)+Θ(n2)T(n) = 7T(n/2) + \Theta(n^2)T(n)=7T(n/2)+Θ(n2)

复杂度分析:根据主方法,a=7a = 7a=7,b=2b = 2b=2,f(n)=Θ(n2)f(n) = \Theta(n^2)f(n)=Θ(n2)。计算 log⁡ba=log⁡27≈2.81\log_b a = \log_2 7 \approx 2.81logb​a=log2​7≈2.81,因此 nlog⁡ba=nlog⁡27≈n2.81n^{\log_b a} = n^{\log_2 7} \approx n^{2.81}nlogb​a=nlog2​7≈n2.81。由于 f(n)=Θ(n2)=O(nlog⁡27−ε)f(n) = \Theta(n^2) = O(n^{\log_2 7 - \varepsilon})f(n)=Θ(n2)=O(nlog2​7−ε)(其中 ε=log⁡27−2≈0.81>0\varepsilon = \log_2 7 - 2 \approx 0.81 > 0ε=log2​7−2≈0.81>0),属于主方法的情况一,因此:

T(n)=Θ(nlog⁡27)=Θ(n2.81)T(n) = \Theta(n^{\log_2 7}) = \Theta(n^{2.81})T(n)=Θ(nlog2​7)=Θ(n2.81)

与标准算法的 Θ(n3)\Theta(n^3)Θ(n3) 相比,Strassen 算法在理论上实现了更优的时间复杂度。对于大规模矩阵运算,这种改进尤为显著。

Strassen 算法的执行步骤:

分解阶段:将矩阵 AAA、BBB、CCC 分别分解为 4 个 (n/2)×(n/2)(n/2) \times (n/2)(n/2)×(n/2) 的子矩阵 A11A_{11}A11​、A12A_{12}A12​、A21A_{21}A21​、A22A_{22}A22​ 和 B11B_{11}B11​、B12B_{12}B12​、B21B_{21}B21​、B22B_{22}B22​。

构造辅助矩阵:创建 10 个辅助矩阵 S1,S2,…,S10S_1, S_2, \ldots, S_{10}S1​,S2​,…,S10​,每个矩阵为子矩阵的线性组合:

S1=B12−B22S2=A11+A12S3=A21+A22S4=B21−B11S5=A11+A22S6=B11+B22S7=A12−A22S8=B21+B22S9=A11−A21S10=B11+B12\begin{align} S_1 &= B_{12} - B_{22} \\ S_2 &= A_{11} + A_{12} \\ S_3 &= A_{21} + A_{22} \\ S_4 &= B_{21} - B_{11} \\ S_5 &= A_{11} + A_{22} \\ S_6 &= B_{11} + B_{22} \\ S_7 &= A_{12} - A_{22} \\ S_8 &= B_{21} + B_{22} \\ S_9 &= A_{11} - A_{21} \\ S_{10} &= B_{11} + B_{12} \end{align}S1​S2​S3​S4​S5​S6​S7​S8​S9​S10​​=B12​−B22​=A11​+A12​=A21​+A22​=B21​−B11​=A11​+A22​=B11​+B22​=A12​−A22​=B21​+B22​=A11​−A21​=B11​+B12​​​

递归计算乘积:递归计算 7 个 (n/2)×(n/2)(n/2) \times (n/2)(n/2)×(n/2) 矩阵的乘积 P1,P2,…,P7P_1, P_2, \ldots, P_7P1​,P2​,…,P7​:

P1=A11⋅S1=A11(B12−B22)P2=S2⋅B22=(A11+A12)B22P3=S3⋅B11=(A21+A22)B11P4=A22⋅S4=A22(B21−B11)P5=S5⋅S6=(A11+A22)(B11+B22)P6=S7⋅S8=(A12−A22)(B21+B22)P7=S9⋅S10=(A11−A21)(B11+B12)\begin{align} P_1 &= A_{11} \cdot S_1 = A_{11}(B_{12} - B_{22}) \\ P_2 &= S_2 \cdot B_{22} = (A_{11} + A_{12})B_{22} \\ P_3 &= S_3 \cdot B_{11} = (A_{21} + A_{22})B_{11} \\ P_4 &= A_{22} \cdot S_4 = A_{22}(B_{21} - B_{11}) \\ P_5 &= S_5 \cdot S_6 = (A_{11} + A_{22})(B_{11} + B_{22}) \\ P_6 &= S_7 \cdot S_8 = (A_{12} - A_{22})(B_{21} + B_{22}) \\ P_7 &= S_9 \cdot S_{10} = (A_{11} - A_{21})(B_{11} + B_{12}) \end{align}P1​P2​P3​P4​P5​P6​P7​​=A11​⋅S1​=A11​(B12​−B22​)=S2​⋅B22​=(A11​+A12​)B22​=S3​⋅B11​=(A21​+A22​)B11​=A22​⋅S4​=A22​(B21​−B11​)=S5​⋅S6​=(A11​+A22​)(B11​+B22​)=S7​⋅S8​=(A12​−A22​)(B21​+B22​)=S9​⋅S10​=(A11​−A21​)(B11​+B12​)​​

合并阶段:通过 PiP_iPi​ 矩阵的线性组合计算 CCC 的四个子矩阵:

C11=P5+P4−P2+P6C12=P1+P2C21=P3+P4C22=P5+P1−P3−P7\begin{align} C_{11} &= P_5 + P_4 - P_2 + P_6 \\ C_{12} &= P_1 + P_2 \\ C_{21} &= P_3 + P_4 \\ C_{22} &= P_5 + P_1 - P_3 - P_7 \end{align}C11​C12​C21​C22​​=P5​+P4​−P2​+P6​=P1​+P2​=P3​+P4​=P5​+P1​−P3​−P7​​​

可通过代数验证,上述公式正确计算了矩阵乘积 C=A⋅BC = A \cdot BC=A⋅B。


求解递推式的方法

分治算法的时间复杂度通常由形如 T(n)=aT(n/b)+f(n)T(n) = aT(n/b) + f(n)T(n)=aT(n/b)+f(n) 的递推关系描述,其中 a≥1a \geq 1a≥1,b>1b > 1b>1,f(n)f(n)f(n) 为渐近非负函数。本节系统介绍求解此类递推关系的三种主要方法。

代入法

代入法(Substitution Method)是一种严格的证明技术,通过数学归纳法验证对递推式解的猜测。其基本步骤为:

  1. 猜测解的形式:基于递归树、经验或类似问题的解,猜测 T(n)T(n)T(n) 的渐近界
  2. 验证猜测:使用数学归纳法证明猜测的正确性,通常需要确定合适的常数

示例:证明 T(n)=2T(n/2)+nT(n) = 2T(n/2) + nT(n)=2T(n/2)+n 的解为 T(n)=O(nlg⁡n)T(n) = O(n \lg n)T(n)=O(nlgn)

猜测:存在常数 c>0c > 0c>0,使得 T(n)≤cnlg⁡nT(n) \leq cn \lg nT(n)≤cnlgn 对所有足够大的 nnn 成立。

归纳证明:假设对于所有 m<nm < nm<n,T(m)≤cmlg⁡mT(m) \leq cm \lg mT(m)≤cmlgm 成立。则:

T(n)=2T(n/2)+n≤2⋅c(n/2)lg⁡(n/2)+n=cnlg⁡(n/2)+n=cn(lg⁡n−lg⁡2)+n=cnlg⁡n−cn+n=cnlg⁡n−(c−1)n\begin{align} T(n) &= 2T(n/2) + n \\ &\leq 2 \cdot c(n/2) \lg(n/2) + n \\ &= cn \lg(n/2) + n \\ &= cn(\lg n - \lg 2) + n \\ &= cn \lg n - cn + n \\ &= cn \lg n - (c-1)n \end{align}T(n)​=2T(n/2)+n≤2⋅c(n/2)lg(n/2)+n=cnlg(n/2)+n=cn(lgn−lg2)+n=cnlgn−cn+n=cnlgn−(c−1)n​​

为使 T(n)≤cnlg⁡nT(n) \leq cn \lg nT(n)≤cnlgn 成立,需要 (c−1)n≥0(c-1)n \geq 0(c−1)n≥0,即 c≥1c \geq 1c≥1。选择 c=1c = 1c=1,并验证基本情况,即可完成证明。

在应用代入法时,必须严格导出所假设的不等式形式。有时需要强化归纳假设,即从猜测中减去一个低阶项(如 T(n)≤cnlg⁡n−dnT(n) \leq cn \lg n - dnT(n)≤cnlgn−dn),以确保归纳步骤能够成立。

递归树法

递归树法(Recursion-Tree Method)通过可视化递推关系来生成解的猜测,特别适用于分析复杂递推式。在递归树中,每个节点表示一个子问题的成本,树的深度对应递归的层数。

示例:分析 T(n)=3T(n/4)+cn2T(n) = 3T(n/4) + cn^2T(n)=3T(n/4)+cn2 的递归树

分析过程:

  1. 树的高度:递归在 n/4i=1n/4^i = 1n/4i=1 时终止,即 i=log⁡4ni = \log_4 ni=log4​n,因此树的高度为 log⁡4n\log_4 nlog4​n
  2. 第 iii 层的成本:第 iii 层有 3i3^i3i 个节点,每个节点的成本为 c(n/4i)2c(n/4^i)^2c(n/4i)2,因此第 iii 层总成本为 3i⋅c(n/4i)2=cn2(3/16)i3^i \cdot c(n/4^i)^2 = cn^2(3/16)^i3i⋅c(n/4i)2=cn2(3/16)i
  3. 总成本:对所有层求和:

T(n)=∑i=0log⁡4ncn2(316)i=cn2∑i=0log⁡4n(316)iT(n) = \sum_{i=0}^{\log_4 n} cn^2\left(\frac{3}{16}\right)^i = cn^2 \sum_{i=0}^{\log_4 n} \left(\frac{3}{16}\right)^iT(n)=∑i=0log4​n​cn2(163​)i=cn2∑i=0log4​n​(163​)i

由于 3/16<13/16 < 13/16<1,该几何级数收敛,其和的上界为常数。因此 T(n)=O(n2)T(n) = O(n^2)T(n)=O(n2)。进一步分析可证明 T(n)=Θ(n2)T(n) = \Theta(n^2)T(n)=Θ(n2)。

递归树法的一般步骤:

构造递归树,标注每个节点的成本

计算树的高度(递归深度)

计算每一层的总成本

对所有层的成本求和,得到总成本的表达式

简化表达式,确定渐近界

主方法

主方法(Master Method)提供了求解形如 T(n)=aT(n/b)+f(n)T(n) = aT(n/b) + f(n)T(n)=aT(n/b)+f(n) 的递推关系的系统化方法,其中 a≥1a \geq 1a≥1,b>1b > 1b>1,f(n)f(n)f(n) 为渐近非负函数。

主定理:设 T(n)=aT(n/b)+f(n)T(n) = aT(n/b) + f(n)T(n)=aT(n/b)+f(n),其中 a≥1a \geq 1a≥1,b>1b > 1b>1,f(n)f(n)f(n) 为渐近非负函数。定义 nlog⁡ban^{\log_b a}nlogb​a,则 T(n)T(n)T(n) 的渐近行为如下:

  • 情况一:若 f(n)=O(nlog⁡ba−ε)f(n) = O(n^{\log_b a - \varepsilon})f(n)=O(nlogb​a−ε),其中 ε>0\varepsilon > 0ε>0,则 T(n)=Θ(nlog⁡ba)T(n) = \Theta(n^{\log_b a})T(n)=Θ(nlogb​a)。
  • 情况二:若 f(n)=Θ(nlog⁡balg⁡kn)f(n) = \Theta(n^{\log_b a} \lg^k n)f(n)=Θ(nlogb​algkn),其中 k≥0k \geq 0k≥0,则 T(n)=Θ(nlog⁡balg⁡k+1n)T(n) = \Theta(n^{\log_b a} \lg^{k+1} n)T(n)=Θ(nlogb​algk+1n)。特别地,当 k=0k = 0k=0 时,T(n)=Θ(nlog⁡balg⁡n)T(n) = \Theta(n^{\log_b a} \lg n)T(n)=Θ(nlogb​algn)。
  • 情况三:若 f(n)=Ω(nlog⁡ba+ε)f(n) = \Omega(n^{\log_b a + \varepsilon})f(n)=Ω(nlogb​a+ε),其中 ε>0\varepsilon > 0ε>0,且对某个常数 c<1c < 1c<1 和所有足够大的 nnn,有 af(n/b)≤cf(n)af(n/b) \leq cf(n)af(n/b)≤cf(n)(正则性条件),则 T(n)=Θ(f(n))T(n) = \Theta(f(n))T(n)=Θ(f(n))。

主方法为分治算法的时间复杂度分析提供了系统化的工具。通过比较 f(n)f(n)f(n) 与 nlog⁡ban^{\log_b a}nlogb​a 的渐近关系,可快速确定算法的复杂度,而无需构造递归树或进行复杂的归纳证明。

应用主方法的步骤

  1. 识别参数:确定 aaa(子问题个数)、bbb(问题规模缩小比例)和 f(n)f(n)f(n)(分解与合并的成本)
  2. 计算关键值:计算 log⁡ba\log_b alogb​a,得到 nlog⁡ban^{\log_b a}nlogb​a
  3. 比较函数:比较 f(n)f(n)f(n) 与 nlog⁡ban^{\log_b a}nlogb​a 的渐近关系
  4. 应用定理:根据比较结果,应用主定理的相应情况

经典应用示例

示例 1:归并排序

  • 递推式:T(n)=2T(n/2)+nT(n) = 2T(n/2) + nT(n)=2T(n/2)+n
  • 参数:a=2a = 2a=2,b=2b = 2b=2,f(n)=nf(n) = nf(n)=n
  • 计算:log⁡22=1\log_2 2 = 1log2​2=1,因此 nlog⁡22=nn^{\log_2 2} = nnlog2​2=n
  • 比较:f(n)=n=Θ(n1)=Θ(nlog⁡22)f(n) = n = \Theta(n^1) = \Theta(n^{\log_2 2})f(n)=n=Θ(n1)=Θ(nlog2​2),属于情况二(k=0k = 0k=0)
  • 结论:T(n)=Θ(nlg⁡n)T(n) = \Theta(n \lg n)T(n)=Θ(nlgn)

示例 2:二分查找

  • 递推式:T(n)=T(n/2)+1T(n) = T(n/2) + 1T(n)=T(n/2)+1
  • 参数:a=1a = 1a=1,b=2b = 2b=2,f(n)=1f(n) = 1f(n)=1
  • 计算:log⁡21=0\log_2 1 = 0log2​1=0,因此 nlog⁡21=1n^{\log_2 1} = 1nlog2​1=1
  • 比较:f(n)=1=Θ(1)=Θ(nlog⁡21)f(n) = 1 = \Theta(1) = \Theta(n^{\log_2 1})f(n)=1=Θ(1)=Θ(nlog2​1),属于情况二(k=0k = 0k=0)
  • 结论:T(n)=Θ(lg⁡n)T(n) = \Theta(\lg n)T(n)=Θ(lgn)

示例 3:Strassen 矩阵乘法

  • 递推式:T(n)=7T(n/2)+Θ(n2)T(n) = 7T(n/2) + \Theta(n^2)T(n)=7T(n/2)+Θ(n2)
  • 参数:a=7a = 7a=7,b=2b = 2b=2,f(n)=Θ(n2)f(n) = \Theta(n^2)f(n)=Θ(n2)
  • 计算:log⁡27≈2.81\log_2 7 \approx 2.81log2​7≈2.81,因此 nlog⁡27≈n2.81n^{\log_2 7} \approx n^{2.81}nlog2​7≈n2.81
  • 比较:f(n)=Θ(n2)=O(n2.81−ε)f(n) = \Theta(n^2) = O(n^{2.81 - \varepsilon})f(n)=Θ(n2)=O(n2.81−ε)(其中 ε=log⁡27−2≈0.81\varepsilon = \log_2 7 - 2 \approx 0.81ε=log2​7−2≈0.81),属于情况一
  • 结论:T(n)=Θ(nlog⁡27)=Θ(n2.81)T(n) = \Theta(n^{\log_2 7}) = \Theta(n^{2.81})T(n)=Θ(nlog2​7)=Θ(n2.81)

主方法仅适用于特定形式的递推关系。若递推式不符合 T(n)=aT(n/b)+f(n)T(n) = aT(n/b) + f(n)T(n)=aT(n/b)+f(n) 的形式,或 f(n)f(n)f(n) 不满足主定理的条件,则需使用递归树法或代入法等其他方法。


小练习

  1. 分治法的适用性分析:判断以下问题是否适合采用分治法求解,并给出理论依据:

    • 计算斐波那契数列的第 nnn 项
    • 寻找数组中的最大值
    • 判断一个字符串是否为回文串
    • 计算两个大整数的乘积
    • 寻找数组中的众数(出现次数最多的元素)
  2. 分治算法设计:设计一个分治算法寻找数组中的第二大元素。要求:

    • 时间复杂度为 O(n)O(n)O(n)
    • 空间复杂度为 O(lg⁡n)O(\lg n)O(lgn)(递归栈空间)
  3. 最近点对问题:给定平面上 nnn 个点,找到距离最近的两个点。

    • 设计一个基于分治的算法
    • 分析算法的时间复杂度
    • 实现算法的关键步骤
  4. 逆序对计数:给定一个数组 A[1..n]A[1..n]A[1..n],计算其中逆序对的数量。逆序对定义为:若 i<ji < ji<j 且 A[i]>A[j]A[i] > A[j]A[i]>A[j],则 (A[i],A[j])(A[i], A[j])(A[i],A[j]) 是一个逆序对。

    • 设计一个基于分治的算法
    • 分析算法的时间复杂度
    • 实现算法的关键步骤
  • 分治法的核心思想
    • 分治法的优势与局限
  • 最大子数组问题
    • 分治求解策略
    • 线性时间解法:Kadane 算法
  • Strassen 矩阵乘法
    • 朴素的分治解法
    • Strassen 算法
  • 求解递推式的方法
    • 代入法
    • 递归树法
    • 主方法
      • 应用主方法的步骤
      • 经典应用示例
  • 小练习

目录

  • 分治法的核心思想
    • 分治法的优势与局限
  • 最大子数组问题
    • 分治求解策略
    • 线性时间解法:Kadane 算法
  • Strassen 矩阵乘法
    • 朴素的分治解法
    • Strassen 算法
  • 求解递推式的方法
    • 代入法
    • 递归树法
    • 主方法
      • 应用主方法的步骤
      • 经典应用示例
  • 小练习
自在学

© 2025 自在学,保留所有权利。

公网安备湘公网安备43020302000292号 | 湘ICP备2025148919号-1

关于我们隐私政策使用条款

© 2025 自在学,保留所有权利。

公网安备湘公网安备43020302000292号湘ICP备2025148919号-1