北邮研究生课程《算法设计与分析》知识总结 - Part 1

6550 字
33 分钟
北邮研究生课程《算法设计与分析》知识总结 - Part 1
2026-06-12

Ch 1. 算法概述#

算法五大基本特性#

  1. 有穷性:有限步骤、有限时间内结束,不能无限执行。
  2. 确定性:每一步操作含义唯一,无歧义。
  3. 可行性:每一步操作都能实际执行完成。
  4. 输入0个或多个输入(问题的原始数据)。
  5. 输出1个或多个输出(问题求解结果)。

三类时间复杂度#

针对同一问题规模NN,输入集合DND_N

  1. 最坏时间复杂度 Tmax(N)T_{max}(N):所有输入中运行时间最大值。
  2. 最好时间复杂度 Tmin(N)T_{min}(N):所有输入中运行时间最小值。
  3. 平均时间复杂度 Tavg(N)T_{avg}(N):按输入概率加权的平均耗时。

渐近复杂性与五大渐近记号#

1. 渐近性态#

NN\to\infty,忽略低阶项、常数项、常数系数,只保留最高阶主项,用于比较算法效率。 例:T(N)=3N2+4NlogN+7T(N)=3N^2+4N\log N+7,渐近性态为 3N23N^2,记作 Θ(N2)\Theta(N^2)

2. 五大渐近记号#

记号名称数学定义(核心)通俗理解
O(g(n))O(g(n))大O(渐近上界)c,n0\exists c,n_0nn0n\ge n_0 时,0f(n)cg(n)0\le f(n)\le c\cdot g(n)f(n)f(n) 增长慢于/等于g(n)g(n)
Ω(g(n))\Omega(g(n))大Ω(渐近下界)c,n0\exists c,n_0nn0n\ge n_0 时,0cg(n)f(n)0\le c\cdot g(n)\le f(n)f(n)f(n) 增长快于/等于g(n)g(n)
Θ(g(n))\Theta(g(n))大Θ(紧渐近界)c1,c2,n0\exists c_1,c_2,n_0c1g(n)f(n)c2g(n)c_1g(n)\le f(n)\le c_2g(n)f(n)f(n)g(n)g(n)同阶(等价)
o(g(n))o(g(n))小o(非紧上界)c>0\forall c>0nn\to\inftyf(n)/g(n)0f(n)/g(n)\to0f(n)f(n) 增长严格慢于g(n)g(n)
ω(g(n))\omega(g(n))小ω(非紧下界)c>0\forall c>0nn\to\inftyg(n)/f(n)0g(n)/f(n)\to0f(n)f(n) 增长严格快于g(n)g(n)

等价简写(选择/判断常考)

  • f=O(g)fgf=O(g) \Leftrightarrow f\le g
  • f=Ω(g)fgf=\Omega(g) \Leftrightarrow f\ge g
  • f=Θ(g)f=gf=\Theta(g) \Leftrightarrow f=g
  • f=o(g)f<gf=o(g) \Leftrightarrow f<g
  • f=ω(g)f>gf=\omega(g) \Leftrightarrow f>g

五大经典算法设计策略#

策略核心思想前提/特点典型例题
分治策略分而治之:拆分子问题→独立求解→合并解子问题独立、结构和原问题一致归并排序
贪心策略每一步选局部最优,期望得到全局最优贪心选择性质 + 最优子结构;无回溯活动选择、资源调度
动态规划记忆化存储子问题解,避免重复计算(以空间换时间)重叠子问题 + 最优子结构最长公共子序列LCS
回溯策略深度优先遍历解空间 + 剪枝剔除无效路径约束满足问题、组合问题N皇后问题、子集和
概率策略引入随机因素,求近似解/高概率正确解NP难问题、大规模近似计算蒙特卡洛求π

NP完全性理论#

  1. P类问题多项式时间可求解的确定型问题(易解)。 例:最短路径(Dijkstra)、排序。
  2. NP类问题多项式时间可验证解(不确定是否多项式可解); 特点:找解难,验证解简单PNPP \subseteq NP。 例:哈密顿回路。

Ch 2. 递归与分治策略#

递归基础#

1. 核心概念#

  1. 递归算法:直接/间接调用自身的算法;递归函数:用自身定义的函数。
  2. 递归两大必备要素
    • 边界条件(终止条件):递归出口,保证有限步结束;
    • 递归方程:问题的递推定义。

    易错点:缺少边界条件会造成无限递归。

2. 递归优缺点#

  • 优点:逻辑清晰、代码简洁、易理解、便于数学归纳法证明正确性;
  • 缺点:调用栈开销大、空间消耗高、调试难度大、运行效率低于非递归。

3. 典型递归函数#

  1. 阶乘函数

    n!={1,n=0n(n1)!,n>0n! = \begin{cases} 1, & n=0 \\ n\cdot (n-1)!, & n>0 \end{cases}
  2. Fibonacci 数列

    F(n)={1,n=0,1F(n1)+F(n2),n>1F(n) = \begin{cases} 1, & n=0,1 \\ F(n-1)+F(n-2), & n>1 \end{cases}

    朴素递归复杂度:O(2n)O(2^n)(低效,大量重复计算);最优线性解法 O(n)O(n)

  3. Ackerman 函数(了解,概念题)

    双递归函数无法转为非递归形式;增长速度极快,其拟逆函数 α(n)\alpha(n) 增长极慢,常规规模下 α(n)4\alpha(n)\le4,常用于复杂度分析。

经典递归实例#

  1. 全排列问题

    思路:依次取每个元素作为首元素,对剩余元素递归求全排列,再拼接结果。

    T(n)={O(1)n=1nT(n1)+O(n)n>1T(n)= \begin{cases} O(1) & n = 1 \\ nT(n-1) + O(n) & n > 1 \end{cases}
  2. 整数划分问题

    增设辅助函数 q(n,m)q(n,m):表示正整数 nn、最大加数不超过 mm 的划分数

    q(n,m)={1,n=1 或 m=1q(n,n),n<m1+q(n,n1),n=mq(n,m1)+q(nm,m),n>m>1q(n,m)= \begin{cases} 1, & n=1 \text{ 或 } m=1 \\ q(n,n), & n<m \\ 1+q(n,n-1), & n=m \\ q(n,m-1)+q(n-m,m), & n>m>1 \end{cases}

    原问题划分数:p(n)=q(n,n)p(n)=q(n,n)

  3. 汉诺塔 (Hanoi) 问题

    三步递归思路:

    1. n1n-1 个圆盘从源柱移到辅助柱;
    2. 将最底部大盘从源柱移到目标柱;
    3. n1n-1 个圆盘从辅助柱移到目标柱。
      • 移动次数递归式:T(n)=2T(n1)+1T(n)=2T(n-1)+1,解为 T(n)=2n1T(n)=2^n-1,指数复杂度。

分治策略总述#

1. 分治与递归的关系#

分治算法天然适合用递归实现;分治拆解出的子问题和原问题结构一致,是递归的典型应用场景,二者常搭配使用。

2. 分治法适用的4个条件#

  1. 问题规模缩小到一定程度后,可直接简单求解
  2. 问题可分解为若干规模更小、同结构的子问题(最优子结构);
  3. 子问题的解可以合并得到原问题的解;
  4. 各子问题相互独立,无公共子问题(可并行计算)。
Note
  • 子问题不独立 → 改用动态规划
  • 无法合并子问题解 → 改用贪心

3. 分治法标准三步流程#

  1. 分解(Divide):将原问题划分为 kk 个规模均等的子问题(优先均分,提升效率);
  2. 求解(Conquer):递归求解每个子问题,子问题足够小时直接求解;
  3. 合并(Merge):将所有子问题的解整合,得到原问题解。

4. 分治算法通用递归复杂度公式#

设:问题规模 nn,分解为 kk 个子问题、每个子问题规模 n/mn/m,分解+合并总耗时 f(n)f(n)

T(n)={O(1),n=1kT(nm)+f(n),n>1T(n)= \begin{cases} O(1), & n=1 \\ k\cdot T(\displaystyle\frac{n}{m}) + f(n), & n>1 \end{cases}

5. mkm、k 对复杂度的影响#

  1. m<km<k:子问题总规模 > 原问题,复杂度偏高(例:Strassen 原始矩阵乘法);
  2. m=km=k:子问题总规模 = 原问题,复杂度适中(例:归并排序);
  3. m>km>k:子问题总规模 < 原问题,复杂度最优(例:二分搜索)。

6. 分治 vs 减治法#

  • 分治法:分解后需要求解多个子问题,再合并所有解(归并排序、最近点对);
  • 减治法:分解后仅需求解一个子问题,无需复杂合并(二分搜索、线性时间选择)。

分治经典例题#

(一)二分搜索#

  1. 问题前提:有序数组,查找指定元素;
  2. 核心思路:每次取中间元素比较,舍去一半区间,仅递归处理一个子区间;
  3. 递归复杂度:
T(n)={O(1),n=1T(n2)+O(1),n>1T(n)= \begin{cases} O(1), & n=1 \\ T(\displaystyle\frac{n}{2}) + O(1), & n>1 \end{cases}
  1. 复杂度结果:最坏/平均/最好:O(logn)O(\log n)
  2. 应用:红黑树、B/B+树、数据库、操作系统调度。

(二)大整数乘法#

用于超长大整数相乘,分治核心:拆分数字 + 减少乘法次数

  1. 普通分治(4次乘法)

    X=a2n2+bX = a \cdot 2^{\frac{n}{2}} + b

    Y=c2n2+dY = c \cdot 2^{\frac{n}{2}} + d

    XY=ac2n+(bc+ad)2n2+bdXY = ac \cdot 2^n + (bc + ad) \cdot 2^{\frac{n}{2}} + bd

    递归式:T(n)=4T(n/2)+O(n)T(n)=4T(n/2)+O(n),复杂度 O(n2)O(n^2),无优化;

  2. 优化分治(3次乘法)

    XY=ac2n+(bc+ad)2n2+bd=ac2n+(bc+ad+acac+bdbd)2n2+bd=ac2n+((bc+adacbd)+ac+bd)2n2+bd=ac2n+((ab)(dc)+ac+bd)2n2+bd\begin{aligned} XY &= ac \cdot 2^n + (bc + ad) \cdot 2^{\frac{n}{2}} + bd \\ &= ac \cdot 2^n + (bc + ad + ac - ac + bd - bd) \cdot 2^{\frac{n}{2}} + bd \\ &= ac \cdot 2^n + \big((bc + ad - ac - bd) + ac + bd\big) \cdot 2^{\frac{n}{2}} + bd \\ &= {\color{red} ac} \cdot 2^n + \big({\color{green} (a - b)(d - c)} + {\color{red} ac} + {\color{blue} bd}\big) \cdot 2^{\frac{n}{2}} + {\color{blue} bd} \end{aligned}

    变形公式减少1次乘法,递归式:T(n)=3T(n/2)+O(n)T(n)=3T(n/2)+O(n) 复杂度:O(nlog23)O(n1.59)\boldsymbol{O(n^{\log_2 3}) \approx O(n^{1.59})},效率提升。

示例

这里以计算 1234×56781234 \times 5678 为例,展示如何利用优化分治(仅需 3 次乘法)来高效完成大整数乘法。

设基数 B=10B = 10,总位数 n=4n = 4,半长 m=n2=2m = \frac{n}{2} = 2。 将两个数按高位和低位拆分:

  • X=1234=12102+34    a=12, b=34X = 1234 = {\color{red} 12} \cdot 10^2 + {\color{blue} 34} \implies a = 12,\ b = 34
  • Y=5678=56102+78    c=56, d=78Y = 5678 = {\color{red} 56} \cdot 10^2 + {\color{blue} 78} \implies c = 56,\ d = 78

根据变形公式,我们只需要计算以下三个乘积:

1. 高位乘积:P1=ac=12×56=6722. 低位乘积:P2=bd=34×78=26523. 交叉项替代:P3=(ab)(dc)=(1234)×(7856)=(22)×22=484\begin{aligned} \text{1. 高位乘积:} \quad & {\color{red} P_1} = {\color{red} ac} = 12 \times 56 = 672 \\ \text{2. 低位乘积:} \quad & {\color{blue} P_2} = {\color{blue} bd} = 34 \times 78 = 2652 \\ \text{3. 交叉项替代:} \quad & {\color{green} P_3} = {\color{green} (a - b)(d - c)} = (12 - 34) \times (78 - 56) = (-22) \times 22 = -484 \end{aligned}

利用 P1,P2,P3P_1, P_2, P_3 还原出中间项 (bc+ad)(bc + ad)

中间项 (bc+ad)=P3+P1+P2=484+672+2652=2840\begin{aligned} \text{中间项 } (bc + ad) &= {\color{green} P_3} + {\color{red} P_1} + {\color{blue} P_2} \\ &= -484 + 672 + 2652 \\ &= 2840 \end{aligned}

(注:传统算法中 bc+ad=34×56+12×78=1904+936=2840bc + ad = 34 \times 56 + 12 \times 78 = 1904 + 936 = 2840,结果一致)

将三部分按权重组合:

XY=P1104+(中间项)102+P2=67210000+2840100+2652=6720000+284000+2652=7006652\begin{aligned} XY &= {\color{red} P_1} \cdot 10^4 + (\text{中间项}) \cdot 10^2 + {\color{blue} P_2} \\ &= 672 \cdot 10000 + 2840 \cdot 100 + 2652 \\ &= 6720000 + 284000 + 2652 \\ &= 7006652 \end{aligned}

(三)Strassen 矩阵乘法#

  1. 普通矩阵乘法:nn 阶矩阵,O(n3)O(n^3)
  2. 标准分块矩阵乘法:8次子矩阵乘法,T(n)=8T(n/2)+O(n2)T(n)=8T(n/2)+O(n^2),仍为 O(n3)O(n^3)
  3. Strassen 优化(7次乘法) 构造7个中间矩阵,减少乘法次数; 递归式:T(n)=7T(n/2)+O(n2)T(n)=7T(n/2)+O(n^2) 复杂度:O(nlog27)O(n2.81)\boldsymbol{O(n^{\log_2 7})\approx O(n^{2.81})}
  4. 特点:大规模矩阵优势明显,小规模常数开销大。

(四)合并排序#

  1. 流程:分解→递归排序左右子数组→合并两个有序数组;
  2. 合并操作:线性时间 O(n)O(n)
  3. 递归式:
T(n)={O(1),n12T(n2)+O(n),n>1T(n)= \begin{cases} O(1), & n\le1 \\ 2T(\displaystyle\frac{n}{2}) + O(n), & n>1 \end{cases}
  1. 复杂度:最好/最坏/平均均为 O(nlogn)O(n\log n)
  2. 空间复杂度:O(n)O(n)(需要辅助数组);
  3. 特点:分解简单、合并复杂;属于最优排序算法(排序下界 Ω(nlogn)\Omega(n\log n))。

(五)快速排序#

  1. 流程:划分(Paitition) → 递归排序左右子区间;无需合并; 划分规则:选基准元素,左小右大。
  2. 复杂度分三种情况(必背)
    • 最好情况:划分均衡(基准为中位数),递归式 T(n)=2T(n/2)+O(n)T(n)=2T(n/2)+O(n)O(nlogn)O(n\log n)
    • 最坏情况:划分极度不平衡(有序/逆序数组,基准为最值),递归式 T(n)=T(n1)+O(n)T(n)=T(n-1)+O(n)O(n2)\boldsymbol{O(n^2)}
    • 平均情况:随机输入,Θ(nlogn)\boldsymbol{\Theta(n\log n)}
  3. 优化方案
    • 随机选择基准元素(RandomizedPartition),降低最坏情况概率;
    • 提前判断数组是否已有序,跳过无效划分。
  4. 特点:原地排序、辅助空间小;不稳定排序。

(六)线性时间选择(求第k小元素)#

问题:在无序数组中找第 kk 小元素(包含最值、中位数)

  1. 随机选择版本

    • 思路:借鉴快排划分,只递归处理目标所在单侧子区间;
    • 平均复杂度:O(n)O(n);最坏复杂度:O(n2)O(n^2)
  2. 最坏线性时间版本 [BFPTR算法]

    基准选取规则:

    1. 数组按每5个元素分组;
    2. 每组取中位数;
    3. 递归求中位数的中位数作为划分基准;
      • 保证划分后子区间长度不超过原长度的 3n/43n/4
      • 递归式:T(n)T(9n/10)+T(n/5)+O(n)T(n)\le T(9n/10)+T(n/5)+O(n),最终复杂度 最坏 O(n)O(n)

(七)平面最接近点对(分治经典应用题)#

  1. 一维情况:数轴上找点对,分治+合并,复杂度 O(nlogn)O(n\log n)
  2. 二维平面情况(核心) 步骤:
    1. 按 x 坐标中位数分割平面为左右两部分;
    2. 递归求左右区域最小距离 d1d2d_1、d_2,令 d=min(d1,d2)d=\min(d_1,d_2)
    3. 只检查分割线左右距离不超过 dd 的点(候选点);
    4. 利用鸽巢原理:每个点最多只需对比 7 个点,合并步骤线性 O(n)O(n)
  3. 总递归式:T(n)=2T(n/2)+O(n)T(n)=2T(n/2)+O(n),复杂度 O(nlogn)\boldsymbol{O(n\log n)}

常用递推式速解#

  1. T(n)=2T(n/2)+O(n)    O(nlogn)T(n)=2T(n/2)+O(n) \implies O(n\log n)(归并、快排平均、最近点对)
  2. T(n)=T(n/2)+O(1)    O(logn)T(n)=T(n/2)+O(1) \implies O(\log n)(二分搜索)
  3. T(n)=T(n1)+O(n)    O(n2)T(n)=T(n-1)+O(n) \implies O(n^2)(快排最坏、汉诺塔变形)
  4. T(n)=3T(n/2)+O(n)    O(nlog23)T(n)=3T(n/2)+O(n) \implies O(n^{\log_2 3})(大整数乘法)
  5. T(n)=7T(n/2)+O(n2)    O(nlog27)T(n)=7T(n/2)+O(n^2) \implies O(n^{\log_2 7})(Strassen矩阵乘法)

Ch 3. 动态规划#

动态规划 基础理论#

1. 核心思想#

动态规划(DP)用于多阶段最优化问题;将大问题拆分为子问题,记录子问题答案、避免重复计算,以空间换时间,把指数复杂度降为多项式复杂度。

Note

对比分治:分治的子问题相互独立;动态规划的子问题大量重叠

2. 两大必备性质#

  1. 最优子结构:原问题的最优解,一定包含其子问题的最优解(DP前提)。
  2. 重叠子问题:递归过程中同一个子问题会被多次计算,DP通过表格存储结果,每个子问题只算一次。

3. 动态规划标准四步设计流程#

  1. 分析最优解结构,验证最优子结构
  2. 定义状态,递归写出最优值递推公式
  3. 自底向上计算所有子问题最优值;
  4. 根据记录表回溯,构造完整最优解

4. 两种实现方式#

  1. 自底向上DP:常规写法,遍历填表,效率高、主流考点;
  2. 备忘录方法(自顶向下):递归+缓存,控制逻辑和纯递归一致,仅重复子问题查表,适合直观理解。

经典例题全解#

(一)矩阵连乘问题#

1. 问题描述

给定 nn 个可连乘矩阵 A1A2AnA_1A_2\cdots A_n,矩阵乘法满足结合律,求最优加括号顺序,使得总的数乘次数最少。 设矩阵 AiA_i 维度:pi1×pip_{i-1} \times p_i

2. 状态定义

m[i,j]m[i,j]:矩阵子链 AiAjA_i \sim A_j 的最少数乘次数; s[i,j]s[i,j]:记录 AiAjA_i \sim A_j 的最优分割位置(用于回溯构造解)。

3. 递推公式

m[i,j]={0, i=j(单个矩阵,无需乘法)minik<j{m[i,k]+m[k+1,j]+pi1pkpj}, i<jm[i,j]= \begin{cases} 0 & ,\ i=j \quad(\text{单个矩阵,无需乘法})\\ \displaystyle \min_{i\le k<j}\big\{m[i,k]+m[k+1,j]+p_{i-1}p_k p_j\big\} & ,\ i<j \end{cases}

4. 复杂度

  • 时间:O(n3)O(n^3)(三层循环)
  • 空间:O(n2)O(n^2)(二维状态表)

5. 关键考点

  • 穷举/分治解法是指数复杂度,DP优化为多项式复杂度;
  • 根据 s[i,j]s[i,j] 回溯输出最终加括号方案;
  • 手算填表。

代码见 matrix_chain_multiplication.py

(二)最长公共子序列 LCS#

1. 基本概念

子序列:元素顺序不变,但可不连续; 公共子序列:同时属于两个序列的子序列,求长度最长的一个。

2. 状态定义 c[i,j]c[i,j]:序列 X1XiX_1\sim X_iY1YjY_1\sim Y_j 的最长公共子序列长度; b[i,j]b[i,j]:记录状态转移方向,用于回溯构造LCS。

3. 递推公式

c[i,j]={0, i=0 或 j=0(空序列)c[i1,j1]+1, xi=yjmax(c[i1,j], c[i,j1]), xiyjc[i,j]= \begin{cases} 0 & ,\ i=0 \ \text{或} \ j=0 \quad(\text{空序列})\\ c[i-1,j-1]+1 & ,\ x_i = y_j \\ \max\big(c[i-1,j],\ c[i,j-1]\big) & ,\ x_i \neq y_j \end{cases}

4. 复杂度与优化

  • 基础版:时间 O(mn)O(mn),空间 O(mn)O(mn)mnm、n 为两序列长度);
  • 空间优化:仅保留两行数组,空间可降至 O(min(m,n))O(\min(m,n))
  • 可省略方向数组,直接由 cc 表回溯。

代码见 longest_common_subsequence.py, LeetCode

(三)最大子段和#

1. 问题描述

给定含正负整数的序列,求连续子数组的最大和;全负数时结果记为0。

2. 状态定义

b[j]b[j]以第 jj 个元素结尾的最大子段和。

3. 递推公式

b[j]=max(b[j1]+a[j], a[j])b[j] = \max\big(b[j-1]+a[j],\ a[j]\big) 最终答案:max{b[1],b[2],,b[n]}\max\{b[1],b[2],\dots,b[n]\}

4. 扩展考点

根据DP状态,回溯求出最大子数组的起止下标

代码见 LeetCode

(四)0-1背包问题#

1. 问题描述
nn 件物品,每件重量 wiw_i、价值 viv_i,背包容量 CC;每件物品最多选1件,求装入物品的最大总价值。

2. 状态定义
dp[i][j]dp[i][j]:从前 ii 件物品中进行选择,且背包容量为 jj 时的最大总价值。
(其中 1in1 \le i \le n0jC0 \le j \le C)

3. 递推公式

dp[i][j]={dp[i1,j], j<wi(剩余容量装不下第i件,只能不选)max(dp[i1,j], dp[i1,jwi]+vi), jwi(在“不选”与“选”中取最大值)dp[i][j]= \begin{cases} dp[i-1,j] & ,\ j < w_i \quad(\text{剩余容量装不下第}i\text{件,只能不选})\\ \max\big(dp[i-1,j],\ dp[i-1,j-w_i]+v_i\big) & ,\ j \ge w_i \quad(\text{在“不选”与“选”中取最大值}) \end{cases}

边界条件

  • dp[0][j]=0( 0jC)dp[0][j] = 0 \quad (\forall\ 0 \le j \le C),表示没有物品可选时,价值为 0。
  • dp[i][0]=0( 1in)dp[i][0] = 0 \quad (\forall\ 1 \le i \le n),表示背包容量为 0 时,价值为 0。

4. 复杂度与特点

  • 时间复杂度O(nC)O(nC),需要填充一个 n×(C+1)n \times (C+1) 的表格。
  • 空间复杂度:基础实现为 O(nC)O(nC)可优化至 O(C)O(C)(使用一维数组 dp[j]dp[j],且内层循环必须逆序遍历 jjCCwiw_i,以防止物品被重复装入)。
  • 算法特点:属于伪多项式时间算法。当背包容量 CC 极大(如 10910^9)而物品总价值较小时,该算法效率会变差(此时可考虑转换状态定义,以“价值”为维度进行 DP)。
  • 核心考点:二维表格的填表计算、一维数组的空间优化(及逆序原因)、通过状态表回溯选出具体装入的物品

(五)流水作业调度(Johnson法则,偏理论+排序应用)#

1. 问题描述

nn 个作业依次经过两台机器 M1M2M_1、M_2 加工(先M1M_1M2M_2),aia_iM1M_1加工时间,bib_iM2M_2加工时间,求作业顺序,使总完工时间最短

2. 核心结论:Johnson不等式 & 调度规则

满足 min(bi,aj)min(bj,ai)\min(b_i,a_j) \ge \min(b_j,a_i) 的顺序为最优顺序,简化排序规则:

  1. 划分两组:

    N1={iai<bi}N_1=\{i\mid a_i < b_i\}N2={iaibi}N_2=\{i\mid a_i \ge b_i\}

  2. N1N_1aia_i 升序排列;

  3. N2N_2bib_i 降序排列;

  4. 最终顺序:N1N_1 作业在前,N2N_2 作业在后。

3. 复杂度

排序为主,时间 O(nlogn)O(n\log n),空间 O(n)O(n)


Ch 4. 贪心算法#

核心基础#

1. 贪心算法思想#

每一步只做当前局部最优选择,期望最终得到全局最优;选择一旦确定就不再回溯,执行方式为自顶向下、迭代缩减问题规模

  • 优势:逻辑简单、实现容易、效率高;
  • 局限:并非所有问题都能得到全局最优,部分场景仅能求出近似解。
  • 反例:特殊硬币找零,贪心选择结果差于全局最优。

2. 适用两大必备性质#

  1. 最优子结构:原问题最优解包含子问题最优(贪心、动态规划共同前提)。
  2. 贪心选择性质:全局最优可通过一系列局部贪心选择逐步得到(贪心独有关键性质)。

3. 贪心通用框架#

解集合S初始为空
while 未得到完整可行解:
从候选集中选出局部最优元素x
满足约束则将x加入S
从候选集移除x
返回S

4. 贪心 vs 动态规划(高频对比题)#

对比维度贪心算法动态规划
求解顺序自顶向下,贪心选择+无回溯自底向上,逐个求解子问题
子问题无重叠,每步直接确定选择存在大量重叠子问题,用空间缓存
选择方式局部最优直接定,不做后续比较枚举所有可能,择优选择
适用代表活动安排、Dijkstra、最小生成树0-1背包、LCS、矩阵连乘
Important

关键区分:0-1背包不能用贪心,可分割的背包问题可以用贪心

经典例题#

(一)两类背包对比#

  1. 0-1背包

    物品不可分割,只能选/不选;无贪心选择性质,只能用动态规划

  2. 可分割背包(分数背包)

    物品可拆分;贪心策略:优先选择单位重量价值最大的物品。 算法复杂度:主要开销为排序,O(nlogn)O(n\log n)

(二)活动安排问题#

  1. 问题:选择最多互不冲突的活动(同一时间仅一个活动占用资源)。

  2. 最优贪心策略:按结束时间升序排序,每次选最早结束的活动。

    原理:留出更多空闲时间,容纳后续活动。

  3. 复杂度:已排序 O(n)O(n);未排序需排序,O(nlogn)O(n\log n)

  4. 最优性证明思路:

    ① 存在包含首个活动的最优解;② 选择后剩余子问题仍最优。

(三)最优装载问题#

  1. 问题:轮船限重,装载最多集装箱。
  2. 贪心策略:优先装重量最轻的集装箱。
  3. 复杂度:排序 O(nlogn)O(n\log n)

(四)Dijkstra 单源最短路径 [代码]#

  1. 问题:带权非负有向图,求源点到所有顶点的最短路径。
  2. 贪心思路:维护已确定最短路径的顶点集合SS,每次从剩余顶点中选出当前路径最短的点加入SS,并更新路径长度。
  3. 复杂度(邻接矩阵实现):O(n2)O(n^2)

(五)哈夫曼编码(最优前缀码)[代码]#

  1. 用途:数据压缩,构造最短平均码长的前缀编码。
  2. 核心规则:使用最小堆,反复合并两个频率最低的节点,生成哈夫曼树。
  3. 关键概念:前缀码(任一编码不是其他编码的前缀)、树总代价 B(T)=f(c)dT(c)B(T)=\sum f(c)\cdot d_T(c)
  4. 复杂度:共n1n-1次合并,堆操作O(nlogn)O(n\log n)

(六)最小生成树(MST)#

最小生成树(Minimum Spanning Tree, 简称 MST)是图论中的一个经典问题。它的核心目标是在一个带权重的无向连通图中,找到一棵包含所有顶点的树,使得这棵树的所有边的权重之和最小。

1. MST 性质

UU是顶点子集,连接UUVUV-U权值最小边,一定属于某一棵最小生成树(反证法证明)。

2. Prim 算法(基于点的贪心) [代码]

  • 思路:从单个顶点出发,每次选取连接树内外权最小的边,逐步扩展生成树。
  • 适用:稠密图;邻接矩阵实现 O(V2)O(V^2),堆优化 O((V+E)logV)O((V+E)\log V)

3. Kruskal 算法(基于边的贪心) [代码]

  • 思路:所有边按权升序,依次选边,用并查集判断是否构成环,无环则加入生成树。
  • 适用:稀疏图;主要开销为边排序,O(ElogE)O(E\log E)

(七)多机调度问题#

  1. 问题:nn个不可中断作业分配到mm台机器,最小化总完工时间(NP完全问题)。
  2. 贪心近似策略:作业按处理时间降序,每次分配给当前负载最小的机器。
  3. 复杂度:排序 O(nlogn)O(n\log n)

Ch 5. 回溯法#

基础概念与核心对比#

1. 回溯法定义#

回溯法是基于深度优先搜索(DFS) 的系统化穷举算法,核心流程:尝试选择 → 递归深入 → 不满足约束则撤销选择(回溯); 本质 = 递归遍历 + 状态选择 + 状态撤销,依靠剪枝规避无效搜索,解决组合爆炸问题。

2. 与其他算法区分(选择/判断高频)#

  • 对比暴力搜索:回溯增加剪枝,提前终止无效路径,效率远高于纯暴力;
  • 对比分治:分治子问题独立、无回溯;回溯子问题关联、需要恢复状态;
  • 对比动态规划:DP存储子问题解避免重复计算;回溯不存子问题,靠剪枝优化。

3. 适用场景#

主要解决约束满足问题、组合枚举、最优解搜索

  1. 排列/组合/子集类问题;
  2. 棋盘问题(N皇后、数独);
  3. 路径、任务分配等带约束的决策问题。

核心术语(名词解释)#

  1. 解空间:问题所有合法解的集合,一般表示为解向量 x=(x0,x1,...,xn1)x=(x_0,x_1,...,x_{n-1}),是所有决策的笛卡尔积。
  2. 解空间树:解的树形表达,根为初始状态,每层对应一步决策,叶子为完整解。
  3. 活结点:已生成、仍有未遍历子节点,可继续扩展。
  4. 死结点:所有子节点遍历完毕,无后续分支,必须回溯。
  5. 扩展结点:当前正在生成子节点的活结点。

回溯标准流程 & 通用模板#

1. 四步执行流程#

  1. 初始化:定义解空间、候选集、约束、路径容器、终止条件;
  2. 递归选择:遍历当前所有候选,选中一个并更新状态;
  3. 剪枝+终止判断:违反约束直接剪枝;到达叶子则记录有效解;
  4. 回溯撤销:移除当前选择、恢复状态,尝试下一个候选。

2. 通用伪代码模板#

Backtrack(状态, 路径, 候选集)
{
if (到达终止条件) {
记录有效解;
return;
}
for (遍历每一个候选选择)
{
if (违反约束) continue; // 剪枝
选择:加入路径、标记状态
Backtrack(新状态, 新路径, 候选集); // 递归
回溯:移除路径、恢复状态
}
}

四大剪枝策略(重中之重)#

剪枝是回溯效率的核心,分为4类,可组合使用;设计原则:前置、精准、低开销

  1. 可行性剪枝(约束剪枝)

    当前路径已不满足问题硬性约束,直接截断分支。 例:N皇后同列/对角线冲突、背包超重。

  2. 最优性剪枝(限界剪枝)

    针对求最优解问题:当前路径代价已差于已知最优解,后续不可能更优,直接剪枝。 例:旅行商、0-1背包求最大价值。

  3. 顺序剪枝

    限制候选选择顺序,避免生成重复解。 例:组合、子集问题用start参数保证下标递增,防止[2,3][3,2]重复。

  4. 数学剪枝

    利用数学规律预判无效路径。 例:组合总和中,数组排序后,当前和+下一个元素已超过目标,后续更大元素直接跳过。

五、经典例题(原理+思路+复杂度+考点)#

1. N皇后问题(可行性剪枝代表)#

问题

N×NN\times N 棋盘放置NN个皇后,任意两个不同行、列、对角线。

约束判定

  • 行:按行放置,天然满足;
  • 列:标记已占用列;
  • 主对角线(行-列 相等)、副对角线(行+列 相等)。

解向量

xix_i 表示第ii行皇后所在列。

复杂度

  • 无剪枝:O(N!)O(N!)
  • 剪枝后:远低于O(N!)O(N!)
  • 空间:O(N)O(N)(路径、标记数组、递归栈)。

2. 组合总和(顺序+数学剪枝结合)#

问题

给定数组,元素可重复选,找出和为target的所有不重复组合。

核心优化

  1. 数组排序,为数学剪枝做铺垫;
  2. start参数控制遍历起点(顺序剪枝,去重);
  3. 当前和+候选值 > target 直接break(数学剪枝)。

复杂度

最坏 O(2k)O(2^k)kk为最大组合长度),剪枝后效率大幅提升;空间 O(k)O(k)

3. 子集问题(顺序剪枝)#

问题

求数组所有子集(含空集),子集不重复。

思路

start保证选取下标严格递增,每进入一层递归就记录当前路径(无需等到叶子)。

复杂度

时间 O(n2n)O(n\cdot 2^n)(共2n2^n个子集,每个子集处理nn);空间 O(n)O(n)

4. 0-1背包(可行性+最优性剪枝)#

问题

物品只能选/不选,背包限重,求最大价值及对应方案。

思路

  1. 每个物品二分支:选 / 不选;
  2. 可行性剪枝:选中后总重量超容量则截断;
  3. 最优性剪枝:当前价值+剩余物品理论最大价值 ≤ 已知最优,直接剪枝。

复杂度

无剪枝 O(2n)O(2^n);剪枝后可处理中等规模数据;空间 O(n)O(n)

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!

赞助
北邮研究生课程《算法设计与分析》知识总结 - Part 1
https://llm-tech.com.cn/posts/bupt-algo-1/
作者
Ming
发布于
2026-06-12
许可协议
CC BY-NC-SA 4.0
最后更新于 2026-06-12,距今已过 39 天

部分内容可能已过时

Profile Image of the Author
Ming
你是来找 Ming 学习的吗
🎉 欢迎来到 Ming 的博客
这里是我的个人博客,分享 AI Infra、LLM 等技术内容。欢迎关注交流!
分类
标签
站点统计
文章
17
分类
10
标签
16
总字数
52,871
运行时长
0
最后活动
0 天前

目录