分治算法(Divide and Conquer)

分治算法是「分解问题」思维模式的典型应用:将大问题分解为独立的子问题,分别解决后合并结果。

定义

分治算法(Divide and Conquer)是一种算法设计范式,将大问题分解成若干个规模较小、相互独立、与原问题性质相同的子问题,递归地解决这些子问题,然后合并其结果得到原问题的解。

核心思想:分而治之——分解(Divide)、解决(Conquer)、合并(Combine)

分治思想 vs 分治算法

维度 分治思想(广义) 分治算法(狭义)
范围 所有分解问题的递归思路 分解后求解效率高于直接求解的递归算法
效率要求 无(有些问题只能分解求解) 必须满足分解后复杂度更低
典型示例 斐波那契递归、二叉树节点计数、动态规划 归并排序、桶排序、合并K个升序链表
直接求解对比 不存在"直接求解"的替代方案 存在直接求解的更低效方案

关键判断标准:是否存在"不分解直接求解"的方案?如果有且分解后更高效,才是分治算法。

复杂度降低原理

数学类比:$(a+b)^2 = a^2 + 2ab + b^2 \geq a^2 + b^2$

  • 原问题规模 $N=a+b$,直接求解 $O(N^2)$ 复杂度为 $O((a+b)^2)$
  • 分解为子问题后分别求解,复杂度为 $O(a^2 + b^2) < O((a+b)^2)$
  • 适用边界:仅多项式级别 $O(N^k)$ 的算法可能通过分治提升效率,指数级算法不适用

核心特性

特性 说明
问题分解 原问题分解为若干子问题
子问题独立 子问题之间没有重叠(区别于动态规划)
递归求解 子问题与原问题性质相同,递归求解
结果合并 将子问题的解合并为原问题的解

分治 vs 动态规划

维度 分治算法 动态规划
子问题关系 独立不重叠 重叠
递归树 每个子问题只计算一次 子问题会重复计算
是否需要记忆化 不需要 通常需要
示例 归并排序、快速排序 斐波那契(带记忆化)

关键区别:分治算法的子问题是独立的,动态规划的子问题是重叠的。

三步骤

  1. 分解(Divide):将原问题分解为若干个规模较小的子问题
  2. 解决(Conquer):递归地解决这些子问题(子问题足够小时直接求解)
  3. 合并(Combine):将子问题的解合并成原问题的解

经典算法

算法 分解方式 合并方式 时间复杂度
归并排序 分成左右两半 合并两个有序数组 O(n log n)
快速排序 按 pivot 分成左右 无需合并(原地排序) 平均 O(n log n)
二分查找 分成左右区间 无需合并(只走一边) O(log n)
最近点对 按 x 坐标分成左右 合并中间区域的候选点 O(n log n)
大整数乘法 按位分解 合并部分积 O(n^1.59)
桶排序 分成若干个桶 对各桶分别排序后合并 O(n)(平均)

归并排序:典型分治

分解:将数组分成左右两半 解决:递归地对左右两半排序 合并:合并两个有序数组

void mergeSort(int[] arr, int left, int right) {
    if (left >= right) return;  // base case
    int mid = left + (right - left) / 2;
    // 分解
    mergeSort(arr, left, mid);
    mergeSort(arr, mid + 1, right);
    // 合并
    merge(arr, left, mid, right);
}

递归树理解:归并排序的递归树是二叉树,后序遍历(先排左右,再合并)。

快速排序:另一种分治

分解:选择 pivot,将数组分成左右两部分 解决:递归地对左右两部分排序 合并:无需合并(原地排序,pivot 已在正确位置)

void quickSort(int[] arr, int left, int right) {
    if (left >= right) return;
    // 分解:partition 返回 pivot 的最终位置
    int p = partition(arr, left, right);
    // 解决:递归排序左右
    quickSort(arr, left, p - 1);
    quickSort(arr, p + 1, right);
    // 合并:无需合并
}

递归树理解:快速排序的递归树是二叉树,前序遍历(先排好 pivot,再排左右)。

适用场景

  • 问题可以分解:原问题能分解为相似的子问题
  • 子问题独立:子问题之间没有依赖关系
  • 合并可行:子问题的解能高效地合并

时间复杂度分析

分治算法的时间复杂度通常用主定理(Master Theorem)分析:

T(n) = aT(n/b) + f(n)

情况 条件 时间复杂度
情况1 f(n) = O(n^c),c < log_b(a) O(n^log_b(a))
情况2 f(n) = Θ(n^log_b(a) * log^k(n)) O(n^log_b(a) * log^(k+1)(n))
情况3 f(n) = Ω(n^c),c > log_b(a) O(f(n))

示例:归并排序 a=2, b=2, f(n)=O(n) → T(n) = O(n log n)

注意事项

  • base case 要正确:递归终止条件必须正确
  • 子问题要独立:这是与动态规划的关键区别
  • 合并操作要高效:如果合并代价太高,分治可能不划算

相关概念

参考资料


Interactive Graph