分治算法(Divide & Conquer)通用思路与伪代码模板

在算法设计里,“分治”(Divide & Conquer)是一类非常经典、也非常实用的思想。许多著名算法,例如归并排序、快速排序、最近点对、快速傅里叶变换(FFT)等,背后都在用分治的套路。
这篇文章不讲某一个具体题目,而是总结分治算法的一般思路,并给出一个可以"套用"的通用伪代码模板,方便写代码时直接代入。

一、什么是 Divide & Conquer?

分治(Divide & Conquer),直译就是"分而治之":把一个规模较大的问题拆成若干个规模更小、形式相同的子问题,分别解决这些子问题后,再把它们的解合并,得到原问题的解。
它的三个关键词可以概括为:

  • Divide(分):拆分问题
  • Conquer(治):递归求解子问题
  • Combine(合):合并子问题的解

只要一个问题满足下面这几个特征,就比较适合用分治:

  • 原问题可以拆分成若干个规模更小但结构相同的子问题。
  • 子问题之间相对独立,也就是彼此之间关联不太大。
  • 有一个明确的"最小规模",当规模足够小时,可以不用再拆,直接解决。
  • 能设计出一个高效的合并步骤,把子问题的答案拼回到原问题的答案上。

如果"分"之后合并的代价太大,或者拆出来的子问题彼此强耦合(高度依赖),那么分治就不一定划算,甚至可能比简单的暴力还慢。

二、分治算法的一般套路

从实现角度看,绝大多数分治算法都可以抽象成对一个"问题实例"的递归函数调用。伪代码的逻辑大致都是下面这四步:

判断是否为基本情况(Base Case)

当问题规模足够小时(例如数组长度为 1、区间只剩一个元素,或者规模小于一个常数阈值),不再继续拆分,直接用简单方法求解。

分(Divide):拆分为子问题

把原问题拆成若干个更小的子问题。

常见方式包括:

  • 区间对半拆分(如 [l, mid][mid+1, r]
  • 按中点划分平面或空间
  • 把数据分成若干块分别处理

治(Conquer):递归求解子问题

对每个子问题,递归调用同一个分治函数,得到子问题的解。

合(Combine):合并子问题结果

根据具体问题的需求,把子问题的解整合起来。

可能是:

  • 简单的拼接(如归并排序的"归并"步骤)
  • 在多个子问题解里取一个最佳的(如取最大值 / 最小值)
  • 结合子问题解再加上一些"跨区"处理(例如最大子段和、最近点对等)

只要清晰地设计出这四步,一个分治算法的框架就基本成型了。

三、通用伪代码模板

下面给出一个比较通用的伪代码模板,可以理解为一个"可配置版"的分治函数。不同的具体问题,只是填入了不同的 Split、DirectSolve、Combine 等函数而已。

// 解决某个抽象的"problem"
function DivideAndConquer(problem):
    // 1. 基本情况:问题足够小,直接解决
    if size(problem) <= THRESHOLD then
        return DirectSolve(problem)

    // 2. 分:把问题拆成若干个子问题
    subProblems = Split(problem)   // 例如拆成 P1, P2, ..., Pk

    // 3. 治:递归求解每个子问题
    subSolutions = empty list
    for each subProblem in subProblems do
        subSolutions.append( DivideAndConquer(subProblem) )

    // 4. 合:把子问题的解合并为原问题的解
    result = Combine(subSolutions)

    return result

其中每一个"占位符"都对应实际代码里要实现的部分:

THRESHOLD

  • 表示"多小的规模算是基本情况"。
  • 比如数组长度为 1、2,或 n <= 16 等。
  • 这个取值有时会影响常数因子,甚至会影响实际运行时间,可以视具体情况微调。

DirectSolve(problem)

  • 对小规模问题,直接用简单方法求解。
  • 典型例子:
    • 归并排序里,长度为 1 的数组不用再排,直接有序。
    • "最大子段和"问题中,区间只剩一个元素时,答案就是这个元素本身。

Split(problem)

  • 把一个问题拆分成若干子问题。
  • 例如:
    • 数组或区间问题里,按中点一分为二。
    • 几何问题里,按一条直线把点集分成左右两半。
    • 字符串问题里,按中点或某个关键下标拆成两段。

Combine(subSolutions)

  • 根据实际问题的定义来组合子解。
  • 例子:
    • 归并排序:将两个有序子数组"归并"为一个更长的有序数组。
    • 最大子段和:在左右子区间答案中取最大值,同时考虑"跨越中点"的子段和。
    • 最近点对:比较左半、右半最优解和跨分割线的候选解,取最小的距离。

可以这样理解:

具体问题的"精髓",就在于如何设计 SplitCombine
而基本框架的递归结构基本不变。

四、和递归、动态规划的关系

分治的实现几乎总是用递归,因此常被简单地等同于"递归算法",但两者不完全等价:

  • 分治更强调:
    • 问题被拆分成相互独立的子问题;
    • 子问题规模更小、形式相同;
    • 有一个合并过程。
  • 动态规划则强调:
    • 子问题往往互相重叠
    • 通过记忆化或表格,自底向上消除重复计算。

有一句常见的"演化关系"可以帮助记忆:

暴力穷举分治(剪枝、组织结构) → 记忆化搜索动态规划

很多题目一开始可以先按照分治思路写出正确但可能较慢的做法,再视情况引入记忆化或改写为 DP,从而去掉重复计算,进一步加速。

五、如何从零开始设计一个分治算法?

面对一个新问题,可以按以下步骤尝试用分治来建模:

  1. 找"规模"的定义
    • 比如是"数组长度 n"、“区间长度 r - l + 1”、"点的数量"等。
  2. 思考"如果规模更小、已经有解了,我能不能把它们拼成大问题的解?"
    • 这一步是在构思 Combine
  3. 再反过来问:“为了让上一步可行,我应该如何拆分原问题?”
    • 这一步是在构思 Split
  4. 最后补齐
    • 定义基本情况(什么时候不再拆)。
    • 对小规模问题的直接解法。
    • 考虑时间复杂度(可以写出递推式,例如 T(n)=aT(n/b)+f(n)T(n) = aT(n/b) + f(n)T(n)=aT(n/b)+f(n))。

在刷题时,可以尝试把常见的分治例题(如归并排序、快速排序、最大子段和、最近点对等)都按照上面的通用模板重新"翻译"一遍,会对 Divide & Conquer 的套路有更深的体感。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值