这篇文章整理了高级算法期末复习的 12 个核心考点,覆盖复杂度分析、渐进记号、递推关系、主定理、堆、快速排序平均分析、对抗策略、线性选择、平摊分析、图搜索、贪心和动态规划。
目录
- 01. 算法的最坏复杂度、平均复杂度和最优性
- 02. 大 O、大 Ω、大 Θ 的定义和相关函数证明
- 03. 递归关系求解
- 04. 平滑函数性质和主定理应用
- 05. 堆的定义、数组表示和堆操作
- 06. 复用快排平均复杂度分析方法处理其他问题
- 07. 对抗策略及经典选择问题下界
- 08. 线性时间选择算法的实现和应用
- 09. 平摊分析、MultiPop Stack、DeleteLargerHalf 和栈模拟队列
- 10. 深度优先、广度优先和图上搜索问题
- 11. 贪心法的思想和特点、换硬币和单源最短路径
- 12. 动态规划的思想和经典问题
01. 算法的最坏复杂度、平均复杂度和最优性
复习资料
01-Introduction.pptx第 20 页:最坏复杂度;第 24-26 页:平均复杂度;第 27-30 页:最优性、上下界高级算法期末复习录音.docx2026高级算法复习.pdf
考点1:最坏复杂度
最坏复杂度是在所有可能输入中,运行时间最长的情况。
要会说明:
- 输入规模为 。
- 不同输入实例可能导致不同运行时间。
- 最坏复杂度取所有实例中的最大运行时间。
例子:在含有 个元素的数组中顺序查找某个元素,最坏情况是元素在最后一个位置,或元素不存在,需要比较 次。
考点2:平均复杂度
平均复杂度是按输入分布对运行代价求期望。若题目没有给概率分布,通常按均匀分布理解。
顺序查找成功时,如果目标等概率出现在 到 的任意位置,平均比较次数为:
如果查找失败,则需要比较 次。
考点3:最优性
证明算法最优通常分两步:
- 证明任何算法完成该任务都至少需要某个下界。
- 证明当前算法正好达到该下界。
也就是:
所以当前算法最优。
例子:从 n 个数中找最大值
顺序扫描算法:
- 先假设第一个元素为当前最大值。
- 依次与后续元素比较。
- 如果发现更大的值,则更新最大值。
- 一共比较 次。
最优性证明:
- 要确认某个元素是最大值,其他 个元素都必须至少输过一次。
- 每次比较最多只能产生一份“输”的信息。
- 因此任何算法至少需要 次比较。
- 顺序扫描算法正好用 次,因此最优。
02. 大 O、大 Ω、大 Θ 的定义和相关函数证明
复习资料
02-Asymptotics.ppt第 4-15 页:渐进增长率、大 O / 大 Ω / 大 Θ 定义和性质高级算法期末复习录音.docx2026高级算法复习.pdf
考点1:什么是大O、大Ω、大Θ
表示上界:
含义:存在正常数 和 ,当 时:
表示下界:
含义:存在正常数 和 ,当 时:
表示同阶:
含义:同时有 和 。
考点2:大O、大Ω、大Θ的性质
根据 02-Asymptotics.ppt 第 14、15 页,重点记下面几类性质。
传递性
如果两个函数之间的渐进关系可以一级一级传下去,那么可以直接合并中间函数。
对偶与对称性质
和 是互相反向的关系:
是真正的同阶关系,因此左右可以交换:
和式性质
两个复杂度类相加时,整体由增长更快的一项主导:
如果 ,也就是 不比 增长更快,那么:
这就是“低阶项可以忽略”的来源。
常数因子可忽略
若 是正常数,则:
也就是说,渐进分析中常数倍不影响复杂度阶。
自反性
任意函数都属于自身的上界类:
考点3:常见增长阶
从慢到快:
其中 ,。
要会比较:
- 和
- 和
- 和
- 多项式项相加时谁主导
03. 递归关系求解
复习资料
03-Recursion.ppt第 12-16 页:形似斐波那契数列的二阶线性齐次递推、特征方程和显式公式高级算法期末复习录音.docx2026高级算法复习.pdf
考点1:解二阶线性齐次递推
重点形式:
复习 PDF 和录音中强调:主要考二阶,且只考两个根不相等的情况。
特征方程
特征方程是把递推关系转成普通代数方程的一种方法。它的核心想法是:先猜这个递推式的解可能长得像一个指数函数:
把它代入递推式:
两边同时除以 ,得到:
整理后就是特征方程:
求根时使用二次方程公式。对于一般二次方程:
两个根为:
对应到这里:
所以:
也就是说,特征方程的根就是“哪些指数底数 可以满足这个递推式”。如果这个方程有两个不同的根 和 ,那么 和 都是这个递推式的基础解。
二阶线性齐次递推的通解就是这两个基础解的线性组合:
其中 、 是两个待定常数,需要用初始条件求出,例如代入 、。
解题步骤
- 从递推式写出特征方程。
- 解二次方程,得到两个根 、。
- 写出通解:
- 代入初始条件,解出 和 。
- 写出最终通项。
斐波那契型例子
对应:
两个根:
所以:
采用常见初值 、,代入通解:
由 得到 ,代入第二个式子:
因为:
所以:
最终结果为:
也就是:
04. 平滑函数性质和主定理应用
复习资料
03-Recursion.ppt第 9 页:平滑函数性质03-Recursion.ppt第 27-30 页:应用主定理求解一般式高级算法期末复习录音.docx2026高级算法复习.pdf
考点1:平滑函数性质
定义
根据 03-Recursion.ppt 第 9 页,设 是定义在自然数集合上的非负、最终非降函数。如果满足:
则称 是平滑函数。
直观理解:当输入规模从 变成 时,函数值只改变一个常数倍量级,不会发生指数级跳变。
作用
平滑函数性质主要用于处理递归式中的向上取整、向下取整,使它们不会影响最终渐进复杂度。
应理解:
- 分治递归中常出现 或 。
- 在满足一定平滑性条件时,可按 来分析渐进阶。
- 考试重点通常不是死背定义,而是知道它为什么能帮助主定理应用。
典型平滑函数
课件中给出的平滑函数包括:
例如:
所以 是平滑函数。
典型非平滑函数
非平滑函数的关键特征是:从 变成 后,函数值不是只差一个常数倍,而是会跨越不同增长量级。
例子 1:指数函数。
则:
比较二者:
这个比值会随着 增大而无限增大,不是常数倍。因此:
所以 不是平滑函数。
例子 2:阶乘函数。
则:
比较二者:
这个乘积会快速增大,不可能被常数限制住。因此:
所以 不是平滑函数。
例子 3:带剧烈振荡的分段函数。
当 时:
但:
所以:
于是:
这个比值也不是常数倍,因此这个函数不是平滑函数。
考点2:应用主定理求解一般式
分治递推的一般形式
根据 03-Recursion.ppt 第 27 页,分治递归的一般形式为:
其中:
- :规模为 的问题所需时间。
- :子问题个数。
- :每个子问题规模。
- :根节点处划分、合并或其他非递归代价。
递推式的意思是:为了解一个规模为 的问题,先递归解决 个规模为 的子问题,再额外花费 的时间做非递归处理。
子问题个数 和子问题规模 都由算法的分治方式决定。它们不是同一个东西,但通常有关联:
- 越大,递归调用越多。
- 越大,单个子问题越小。
- 每一层递归的子问题总规模可以粗略看成 。
例如归并排序把数组分成 个规模为 的子问题,所以 、;二分查找只递归处理 个规模为 的子问题,所以 、。
递归树深度与临界指数
设基本情形出现在深度 ,则:
所以:
叶子数为:
经过代数化简:
其中:
称为临界指数。主定理的核心就是比较:
也就是比较根节点非递归代价和叶子层总代价的增长速度。
行和思想
根据 03-Recursion.ppt 第 28-29 页,递归树的解等于所有节点的非递归代价之和,也可以看成每一层行和之和。
- 第 层行和是 。
- 第 层行和是 。
- 如果行和逐层增大,最后一层主导。
- 如果每层行和同阶,层数乘以单层代价。
- 如果行和逐层减小,第一层主导。
主定理三种情况
根据 03-Recursion.ppt 第 30 页:
情况 1:叶子层或子问题代价主导。
则:
情况 2:每层代价均衡。
则:
因为此时每层贡献差不多,递归树深度是 。
情况 3:根节点非递归代价主导。
并且课件给出一个上界限制:
则:
课件强调:正的 很关键,它会导致三种情况之间存在间隙。
三种情况的例子
情况 1 例子:
这里 、、,先算:
所以:
因为:
所以属于情况 1:
情况 2 例子:
这里 、、,先算:
所以:
因为:
所以属于情况 2:
这就是归并排序的复杂度形式。
情况 3 例子:
这里 、、,先算:
所以:
因为:
所以属于情况 3:
规整条件补充
有些教材在第三种情况中还会要求规整条件:
其中 ,并且当 足够大时成立。
它的直观含义是:下一层所有子问题的非递归代价加起来,必须比当前层非递归代价小一个固定比例。
对于:
有:
因此可取 ,满足规整条件。
不满足规整条件的例子:
其中:
这里 、,所以 。由于 至少是 量级,看起来像主定理第三种情况。
但取 ,此时:
而 ,是奇数次幂,因此:
所以:
若要满足规整条件,就需要:
也就是:
当 足够大时,不可能存在固定的 满足它。因此这个例子不满足规整条件,不能直接套用主定理第三种情况。
从题意翻译递推式
例子:一个规模为 的问题,分成 个规模为 的子问题,合并代价为 。
递推式:
比较:
因为 更大,通常进入主定理第三种情况,得到:
05. 堆的定义、数组表示和堆操作
复习资料
06-HeapSort.ppt第 5-15 页:堆的定义、堆排序策略、fixHeap、建堆、数组表示高级算法期末复习录音.docx2026高级算法复习.pdf
考点1:堆的定义
堆结构
根据 06-HeapSort.ppt 第 5 页,二叉树 是堆结构,需要满足:
- 至少到深度 都是满的。
- 所有叶子都在深度 或 。
- 深度为 的叶子路径都位于深度为 的叶子路径左边。
直观理解:堆首先是一棵“尽量从上到下、从左到右填满”的二叉树,也就是通常所说的完全二叉树。
偏序树性质
课件讨论的是最大堆。若一棵树满足最大偏序树性质,则任意节点的关键字都大于或等于它每个子节点的关键字:
因此最大堆的根节点一定保存整棵堆中的最大关键字:
堆的完整定义
堆等于:
也就是说,堆既要有接近完全二叉树的形状,又要满足父节点不小于子节点的顺序要求。
若是最小堆,则不等号方向相反:
考点2:用数组表示堆
为什么可以用数组表示
堆结构接近完全二叉树,节点位置非常规整,所以不需要显式保存左右指针。只要把节点按层序从左到右放入数组,就可以通过下标快速找到父节点和子节点。
课件中的 1 下标表示
06-HeapSort.ppt 第 15 页使用从 开始的数组下标。
对于数组元素 :
因此:
- 是堆顶。
- 是 的左孩子。
- 是 的右孩子。
- 是 的父节点。
0 下标补充
如果程序数组从 开始,则公式改为:
考试或写伪代码时要先看清下标从 开始还是从 开始。
考点3:实现堆操作
堆排序策略
根据 06-HeapSort.ppt 第 7 页,堆排序的基本策略是:
- 由待排序元素集合 构造堆 。
- 每次取出堆顶最大元素。
- 删除堆顶后修复堆。
- 将取出的最大元素从数组末尾向前放。
伪代码思想为:
deleteMax
最大堆中最大元素总在根节点。删除最大元素时,不能直接空出根节点,否则堆结构会被破坏。
课件第 7 页给出的思路是:
- 把最低层最右边的元素复制为 。
- 删除最低层最右边的节点。
- 根节点位置变成空位。
- 调用
fixHeap(H,K),把 放到合适位置,同时恢复偏序树性质。
fixHeap
根据 06-HeapSort.ppt 第 8 页,fixHeap(H,K) 的输入条件是:
- 是非空二叉树。
- 根节点位置暂时为空。
- 左子树和右子树已经满足偏序树性质。
- 有一个元素 需要插入到堆中。
目标是把 插入到合适位置,使整棵树重新满足最大堆性质。
基本过程:
- 如果 是叶子节点,直接把 放入根节点。
- 否则,在左右子树根节点中选出关键字较大的那个,记为
largerSubHeap。 - 如果 的关键字大于等于
largerSubHeap的根关键字,则把 放入当前根节点。 - 否则,把
largerSubHeap的根节点上移到当前根节点。 - 空位移动到
largerSubHeap的根位置,继续递归执行fixHeap(largerSubHeap,K)。
这个过程可以理解为:空位一路向下移动,每次让较大的孩子上移,直到 能放在不破坏堆序的位置。
fixHeap 的复杂度
根据 06-HeapSort.ppt 第 10 页,fixHeap 每次递归最多做两次比较:
- 一次比较左右孩子,找出较大的子堆。
- 一次比较 和较大孩子的根。
每次递归树高减少 ,因此最坏情况下比较次数不超过:
其中 是堆的高度。由于堆是完全二叉树:
所以:
建堆 constructHeap
根据 06-HeapSort.ppt 第 11-12 页,如果左右子树都已经满足偏序树性质,那么对当前根调用:
就可以让当前子树也满足偏序树性质。
因此建堆可以采用后序遍历思想:
- 先递归处理左子树。
- 再递归处理右子树。
- 最后对根节点执行
fixHeap。
伪代码思想为:
建堆复杂度
06-HeapSort.ppt 第 13 页说明:建堆总时间是线性的。
虽然单次 fixHeap 最坏是 ,但建堆时不是每个节点都会下滤整棵树高度:
- 靠近叶子的节点很多,但高度很小。
- 靠近根的节点高度大,但数量很少。
把所有节点的下滤代价加起来,总复杂度为:
堆排序总复杂度为:
06. 复用快排平均复杂度分析方法处理其他问题
复习资料
04-Quicksort.ppt第 28-35 页:快排平均比较次数递推式及其求解高级算法期末复习录音.docx2026高级算法复习.pdf
考点:复用快排平均复杂分析方法
讲解例子:快排平均比较次数
随机划分模型
根据 04-Quicksort.ppt 第 28 页,快排选定分割元素 splitPoint 后,会把剩余元素分成两个子区间。
若输入规模为 ,splitPoint 的位置记为 ,则:
- 左子问题规模为 。
- 右子问题规模为 。
- 。
- 随机情况下,每个 出现的概率都是 。
第一轮为了找出 splitPoint 的位置,需要把它和其余 个元素比较,所以当前层比较次数是:
平均比较次数递推式
设 表示快速排序处理 个元素时的平均比较次数,并且:
当分割点位置为 时,递归子问题代价为:
对所有可能的 取平均,得到:
由于 和 只是同一组值正向、反向各出现一次,所以:
又因为 ,所以可化简为:
也可以写成:
这里的理解是:
- : 种 pivot 情况下,本层划分比较次数的总和。
- : 种 pivot 情况下,左右子问题平均递归代价的总和。
- 最后除以 :对 种等概率情况取平均。
根据递推式推出复杂度
根据 04-Quicksort.ppt 第 34-35 页,可以直接从递推式推出平均复杂度。
已知:
对应地:
把第一个式子乘以 ,第二个式子乘以 ,再相减:
中间的求和项大部分抵消,只剩下:
所以:
两边除以 :
令:
则:
又因为 ,所以 。展开得到:
利用:
可以化为:
其中调和级数满足:
因此:
而:
所以:
更精确地说,快排的平均比较次数是 量级。PPT 第 35 页还给出近似形式:
本节要复用的不是“排序”这个动作,而是下面这个分析模式:
如果某个问题每一轮的具体比较次数不是 ,而是 ,则递推式变成:
同样用第 34-35 页的方法,可得:
令:
则:
因为:
所以:
从而:
可能考题1:螺丝螺母配对
问题描述
有 个螺丝和 个螺母,每个螺丝恰好匹配一个螺母。
限制条件是:
- 螺丝之间不能互相比较。
- 螺母之间不能互相比较。
- 只能拿一个螺丝和一个螺母比较,判断螺丝是偏大、偏小,还是刚好匹配。
目标是找出全部螺丝和螺母的匹配关系。
算法思想
这个问题可以复用快排划分思想:
- 随机选一个螺丝作为 pivot。
- 用这个螺丝和所有螺母比较,把螺母分成小于、匹配、大于三类。
- 找到与 pivot 螺丝匹配的螺母。
- 再用这个匹配螺母去划分所有螺丝,把螺丝也分成小于、匹配、大于三类。
- 左边螺丝只可能匹配左边螺母,右边螺丝只可能匹配右边螺母。
- 对左右两个子问题递归处理。
递推式和复杂度
螺丝螺母配对中:
- pivot 是随机选出的螺丝。
- 一轮操作能把问题划分成左右两个子问题。
- pivot 匹配项的位置等概率落在 。
每一轮的具体比较次数是:
其中:
- 用 pivot 螺丝比较全部 个螺母,需要 次比较。
- 找到匹配螺母后,用它比较剩下 个螺丝,需要 次比较。
如果 pivot 匹配项的位置为 ,则左子问题规模为 ,右子问题规模为 。
设 表示平均比较次数,则:
也就是:
这个式子和快排平均比较次数递推式同型,只是本层代价从 变成 。根据前面的直接推导:
可能考题2:同学和衣服尺码匹配
问题描述
有 个同学和 件衣服,每个同学恰好有一件合适尺码的衣服。
在不知道具体尺码数字的情况下,允许做的比较是:让某个同学试某件衣服,判断衣服对这个同学来说:
- 偏小。
- 正好合适。
- 偏大。
目标是为每个同学匹配对应的衣服。
这个问题和螺丝螺母配对本质相同:
算法思想
- 随机选一个同学作为 pivot。
- 让该同学试所有衣服,将衣服分成偏小、合适、偏大三类。
- 找到该同学对应的合适衣服。
- 用这件衣服去和所有同学比较,将同学分成需要更小衣服、正好匹配、需要更大衣服三类。
- 偏小衣服集合只可能匹配需要更小衣服的同学集合。
- 偏大衣服集合只可能匹配需要更大衣服的同学集合。
- 分别递归处理左右两个子问题。
递推式和复杂度
设 表示为 个同学匹配 件衣服的平均比较次数。
每一轮的具体比较次数是:
其中:
- pivot 同学试全部 件衣服,需要 次比较。
- 找到合适衣服后,用这件衣服比较剩下 个同学,需要 次比较。
pivot 同学对应衣服的相对位置等概率落在所有可能位置上。如果该位置为 ,则左子问题规模为 ,右子问题规模为 。
所以平均递推式为:
化简后:
该递推式和螺丝螺母配对完全同型,因此:
所以,在只能跨集合比较、不能直接知道尺码数值的情况下,仍然可以用类似快排平均分析的方法,得到平均时间复杂度:
07. 对抗策略及经典选择问题下界
复习资料
07-Selection.ppt第 3-17 页:最大最小、对抗策略、第二大元素及下界高级算法期末复习录音.docx2026高级算法复习.pdf
考点1:对抗策略的含义
选择问题背景
根据 07-Selection.ppt 第 4 页,选择问题是:
给定包含 个关键字的集合 ,以及整数 ,满足:
要求找出第 小的元素。
找最大值和最小值是选择问题的特殊情况:
下界证明的基本想法
对抗策略用于证明比较类算法的下界。它不是先固定一个输入,而是假设有一个“对手”在算法比较两个元素时回答比较结果。
对手的目标是:
- 始终保持回答前后一致。
- 尽量让算法每次比较获得更少的新信息。
- 迫使算法做更多比较后,才能唯一确定答案。
因此,对抗策略常用于证明:
找最大值的下界
07-Selection.ppt 第 5 页给出找最大值的基本下界。
要确定某个元素 是最大值,必须知道其他 个元素都不可能是最大值。一个元素只有在某次比较中输给别人后,才能被排除为最大值候选。
每次比较最多产生一个“输者”,所以至少需要:
次比较才能找出最大值。
这也是普通扫描找最大值的比较次数,因此找最大值的最坏情况复杂度下界是紧的。
考点2:同时求最大值和最小值
算法思想
根据 07-Selection.ppt 第 7 页,同时求最大值和最小值可以通过两两分组减少比较次数。
算法步骤:
- 先把元素两两配对比较。
- 每组较大的元素进入最大值候选集合。
- 每组较小的元素进入最小值候选集合。
- 在最大值候选集合中找最大值。
- 在最小值候选集合中找最小值。
比较次数
若 为偶数,第一次分组比较次数为:
最大值候选集合有 个元素,找最大值需要:
最小值候选集合也有 个元素,找最小值需要:
总比较次数为:
若 为奇数,课件给出的比较次数为:
对抗策略下界
07-Selection.ppt 第 8-10 页用信息单位解释下界。
要确定最大值,需要知道除最大值外,每个元素都输过一次。要确定最小值,需要知道除最小值外,每个元素都赢过一次。
因此总共需要获得:
个信息单位。
对抗者可以把每个元素的状态分为:
- :还没有赢过,也没有输过。
- 已经赢过:不可能是最小值,但仍可能是最大值。
- 已经输过:不可能是最大值,但仍可能是最小值。
对抗策略的原则是:
- 如果两个元素都是 ,比较后一个赢、一个输,算法得到 个新信息单位。
- 如果不是两个 相比,对抗者尽量只让算法得到 个新信息单位。
当 为偶数时,最多只有最开始的 次配对比较能贡献 个信息单位。其余还需要:
个信息单位,每次比较最多给 个。
所以比较次数下界为:
这与前面的算法比较次数一致,因此该算法是最优的。
考点3:求第二大元素
朴素方法
根据 07-Selection.ppt 第 12 页,直接调用两次找最大值可以求第二大元素:
- 第一次找最大值。
- 删除最大值。
- 第二次在剩余元素中找最大值,也就是原集合的第二大元素。
比较次数为:
这个方法正确,但没有充分利用第一次找最大值过程中得到的信息。
锦标赛法
07-Selection.ppt 第 13-14 页给出更优方法:锦标赛法。
算法思想:
- 像淘汰赛一样两两比较,较大者晋级。
- 最终冠军就是最大值。
- 第二大元素只可能出现在“直接输给最大值”的元素中。
- 在这些直接输给最大值的元素中再找最大值。
直观原因是:如果某个元素输给的不是最大值,那么它至少输给了一个非最大元素,因此不可能是第二大。
比较次数
找最大值的锦标赛需要:
次比较。
最大值从叶子一路晋级到根,最多直接打败:
个元素。
第二大元素只可能在这些候选者中,所以再找最大需要:
次比较。
总比较次数为:
若 是 的幂,则最大值直接比较过的元素个数正好是:
对抗策略下界
07-Selection.ppt 第 15-17 页说明,任何比较算法求第二大元素,在最坏情况下都至少需要:
次比较。
证明思路:
- 要找第二大,必须先确定最大值,因此至少需要 次比较。
- 第二大元素只能是直接输给最大值的元素。
- 对抗者可以迫使最大值至少直接赢过 个不同元素。
- 要从这 个候选者里找最大,还需要 次比较。
课件中的权重法可以理解为:
- 每个元素初始权重为 。
- 当某个元素赢过一个此前没有输过的元素时,它的权重最多翻倍。
- 如果最终 是最大值,它必须成为唯一仍可能是最大值的元素,因此它的权重需要达到 。
若最大值经历了 次这种关键胜利,则:
所以:
因此最大值至少直接击败过 个候选者。算法必须在这些候选者中继续找最大,所以下界与锦标赛算法一致,锦标赛法是最优的。
08. 线性时间选择算法的实现和应用
复习资料
07-Selection.ppt第 19-27 页:选择算法、划分策略、中位数的中位数、最坏情况线性复杂度高级算法期末复习录音.docx2026高级算法复习.pdf
考点1:选择问题和第 k 大值转换
选择问题
根据 07-Selection.ppt 第 22 页,选择问题是:
给定集合 ,其中有 个关键字,再给定整数 ,满足:
要求找出 中第 小的关键字。
中位数选择只是选择问题的特殊情况:
第 k 大和第 k 小的转换
课件默认讨论第 小。如果题目问“第 大”,可以转换成第几小。
在 个元素中,第 大等价于第:
小。
例如在 个元素中:
- 第 大是第 小。
- 第 大是第 小。
- 第 大是第 小。
所以求第 大时,可以调用选择算法:
为什么只递归一边
07-Selection.ppt 第 19-20 页说明,如果能把集合划分成两个子集 和 ,并满足:
那么目标元素只会落在其中一边。
若要找第 小:
- 如果 ,答案在 中。
- 如果 ,答案就是 pivot。
- 如果 ,答案在 中,并且新的排名变为:
因此选择问题和快速排序不同:快速排序要递归左右两边,而选择算法只递归一边。
考点2:线性时间选择算法
坏 pivot 的问题
07-Selection.ppt 第 21-22 页指出,如果 pivot 很差,划分会非常不均匀,递归规模可能接近 ,这会让选择算法退化。
所以线性时间选择算法的关键是:构造一个足够好的 pivot,使每轮至少丢掉固定比例的元素。
Median of Medians 思想
07-Selection.ppt 第 23-24 页给出的改进策略是中位数的中位数。
步骤如下:
- 将所有元素每 个分为一组。
- 在每组内部排序。
- 找出每组的中位数。
- 递归地在这些组中位数中找中位数,记为 。
- 用 作为 pivot 对原集合划分。
这里的 就是 median of medians。
构造划分
用 作为 pivot 后,把元素划分为:
课件第 24 页用图中区域说明:由于 是组中位数的中位数,至少有相当一部分元素能确定小于 ,也至少有相当一部分元素能确定大于 。
这保证了递归处理的一边不会太大,最坏情况下大约不超过:
递归过程
根据 07-Selection.ppt 第 25 页:
若要找第 小,划分后:
也就是说,每轮只进入一个子问题。
伪代码结构
考点3:复杂度分析和应用
比较次数来源
根据 07-Selection.ppt 第 26 页,线性选择算法每轮主要有三部分代价:
- 每 个元素一组,找每组中位数。
- 递归找这些组中位数的中位数。
- 用 对原集合做划分。
因此递推式可以写成:
其中:
- :递归寻找中位数的中位数。
- :最坏情况下,继续递归处理较大的那一边。
- :分组、组内处理、划分等线性代价。
常见教材也会把它写成近似形式:
为什么是线性
07-Selection.ppt 第 27 页用递归树行和说明该递推式是线性的。
两条递归分支规模加起来大约是:
所以下一层的总规模至多约为上一层的 倍。递归树每层线性代价形成递减几何级数:
该级数和为:
因此:
这就是“最坏情况下线性时间选择算法”的核心结论。
应用
线性时间选择算法可以用于:
- 找第 小元素。
- 找第 大元素。
- 找中位数。
- 找分位数。
- 找阈值,例如保留较小的一半、删除较大的一半。
例如,要找第 大元素,可以先转换成第 小:
要删除较大的一半,可以先在线性时间内找出中位数,再用一次线性扫描删除大于中位数的元素。总时间仍为:
09. 平摊分析、MultiPop Stack、DeleteLargerHalf 和栈模拟队列
复习资料
08-HashTable.ppt第 15-20 页:数组扩张和平摊分析、MultiPop StackAmortized Analysis.pdf:平摊分析补充例题高级算法期末复习录音.docx2026高级算法复习.pdf
考点1:平摊分析的含义
基本思想
平摊分析研究的是一串操作的总成本,而不是只看单次操作的最坏成本。
如果一个数据结构中存在某些“偶尔很贵”的操作,单独看最坏情况可能会把复杂度估得过高。平摊分析会把这些少数高成本操作分摊到整个操作序列上,得到每次操作的平均保证。
设一串操作共有 次,实际总代价为:
则单次操作的平摊代价可以理解为:
注意:平摊分析不是概率分析,不要求输入随机,也不取期望。它给的是任意操作序列上的总成本保证。
适用场景
平摊分析适合以下情况:
- 单次操作最坏情况很贵。
- 贵操作不可能连续频繁发生。
- 贵操作之前通常积累了很多便宜操作。
- 可以证明一串操作的总成本较低。
08-HashTable.ppt 第 15-18 页的数组扩张就是典型例子:某次插入触发扩容,需要搬移很多元素,但扩容只在容量满时发生,不能每次都发生。
常见分析方法
常见方法有三种:
- 聚合分析:直接证明 次操作的总成本是 ,再除以 。
- 记账法:给便宜操作多收一些“代币”,以后用这些代币支付贵操作。
- 势能法:定义数据结构的势能 ,把未来可能发生的成本存进势能中。
课件第 18 页给出记账法的表达式:
设计平摊代价时,要保证账户或势能不会欠债,这样总平摊成本才能覆盖总实际成本。
考点2:MultiPop Stack
操作定义
栈支持三个操作:
Push(x):把元素 压栈。Pop():弹出栈顶元素。MultiPop(k):连续弹出最多 个元素,若栈中元素少于 个,则弹到空栈为止。
若当前栈大小为 ,则:
所以单次 MultiPop(k) 的最坏情况可能是 。
聚合分析
考虑任意一串栈操作。每个元素最多经历:
- 被
Push入栈一次。 - 被
Pop或MultiPop弹出一次。
元素一旦被弹出,就不会再为之后的弹出操作付费。
如果一共执行 次操作,那么压栈次数至多为 ,弹出元素总数也至多为 。因此总成本为:
所以每次操作的平摊成本为:
记账法
08-HashTable.ppt 第 19 页给出一种记账方式:
Push的平摊成本记为 。Pop和MultiPop的平摊成本记为 。
解释:
Push实际花费 ,多收的 个代币存在被压入的元素上。- 以后该元素被
Pop或MultiPop弹出时,用它身上的代币支付弹出成本。 - 每个元素只会被弹出一次,所以代币足够。
因此 Push、Pop、MultiPop 的平摊复杂度都是:
考点3:DeleteLargerHalf
问题描述
维护一个集合 ,支持:
Insert(S,x):插入元素 。DeleteLargerHalf(S):删除集合中较大的一半元素。
如果当前集合大小为 ,DeleteLargerHalf 会删除大约:
个较大元素。
实现思路
可以借助第 8 章的线性时间选择算法:
- 用
select在线性时间内找到中位数附近的阈值。 - 扫描集合,把大于阈值的一半元素删除。
- 保留较小的一半元素。
一次 DeleteLargerHalf 的实际成本可以记为:
平摊分析
虽然单次 DeleteLargerHalf 可能很贵,但它会一次删除当前集合的一半元素。
可以使用记账法:
- 每次
Insert多收常数个代币,存在新插入元素身上。 - 当元素在某次
DeleteLargerHalf中被删除时,用它之前存下的代币支付删除和扫描的一部分成本。 - 一个元素从插入到被删除,最多只会被删除一次。
因此,在一串操作中,删除成本可以由之前插入的元素支付。
若共有 次操作,则总成本为:
所以每次操作的平摊成本为:
这里要注意:如果实现中每次删除都用线性选择找到阈值,那么这次线性扫描成本被当前集合中即将保留和删除的元素共同承担。由于删除操作会显著减少集合规模,后续必须经过足够多次插入,集合规模才会再次变大。
考点4:两个栈模拟队列
实现方法
用两个栈实现队列:
- 输入栈
Stack_in:负责入队。 - 输出栈
Stack_out:负责出队。
入队 Enqueue(x):
出队 Dequeue():
- 如果
Stack_out非空,直接从Stack_out弹出。 - 如果
Stack_out为空,把Stack_in中所有元素依次弹出并压入Stack_out。 - 再从
Stack_out弹出队首元素。
为什么顺序正确
栈是后进先出。元素先进入 Stack_in,当整体搬运到 Stack_out 时,顺序会反转一次。
一次反转后,最早进入队列的元素会位于 Stack_out 栈顶,因此出队顺序正好符合先进先出。
平摊分析
每个元素最多经历以下栈操作:
- 压入
Stack_in一次。 - 从
Stack_in弹出一次。 - 压入
Stack_out一次。 - 从
Stack_out弹出一次。
因此每个元素最多参与常数次基本栈操作。
若一共处理 个入队和出队操作,总成本为:
所以单次队列操作的平摊成本为:
10. 深度优先、广度优先和图上搜索问题
复习资料
10-DAGApp.ppt第 5-34 页:DAG、拓扑序、任务调度、关键路径、强连通分量09-GTraverse.ppt:DFS 和 BFS 基本思想高级算法期末复习录音.docx2026高级算法复习.pdf
考点1:DFS 和 BFS 的思想
DFS
深度优先搜索的思想是:从一个顶点出发,沿着未访问的边尽量向深处走;当当前顶点没有未访问邻接点时,再回溯。
DFS 常用颜色标记:
- 白色:尚未发现。
- 灰色:已经发现,但邻接点还没有处理完。
- 黑色:已经处理完成。
DFS 中常记录:
- 发现时间。
- 完成时间。
- 父节点。
- DFS 树或森林。
很多有向图算法依赖 DFS 的完成时间,例如拓扑排序和强连通分量。
BFS
广度优先搜索的思想是:从源点开始,按距离层次一层一层向外扩展。
BFS 通常使用队列:
- 源点入队。
- 每次取出队首顶点。
- 把它尚未访问的邻接点加入队尾。
- 重复直到队列为空。
BFS 适合解决:
- 无权图最短路径。
- 层次遍历。
- 可达性判断。
复杂度
若图用邻接表表示,DFS 和 BFS 都会访问每个顶点和每条边常数次,所以复杂度为:
其中 ,。
考点2:拓扑排序和任务调度
DAG 和拓扑序
根据 10-DAGApp.ppt 第 5-6 页,DAG 是有向无环图。
给定有向图:
拓扑序是给每个顶点分配不同编号 ,使得对于每条有向边:
都有:
也就是说,边的起点必须排在终点前面。
何时存在拓扑序
10-DAGApp.ppt 第 7 和第 12 页说明:
如果图中有有向环,那么环上的顶点需要互相排在对方前面,会产生矛盾,因此不存在拓扑序。
DFS 求拓扑序
课件第 8-11 页给出基于 DFS 的反向拓扑序算法。
核心思想:
- 对图做 DFS。
- 在顶点完成时进行后序处理。
- 完成时间越晚的顶点,在拓扑序中越靠前。
若使用计数器 topoNum 在后序阶段递增,则得到的是反向拓扑序。若想得到普通拓扑序,可以按完成时间递减排列。
复杂度为:
任务调度
10-DAGApp.ppt 第 13-14 页把任务调度建模为 DAG。
建模方式:
- 顶点表示任务。
- 有向边表示依赖关系。
- 若任务 依赖任务 ,则必须先完成 再做 。
只要依赖图是 DAG,就可以按拓扑序安排任务。若依赖图有环,则任务之间存在循环依赖,无法完成合法调度。
考点3:关键路径
基本概念
10-DAGApp.ppt 第 15-17 页介绍关键路径。它适用于带任务时长的 DAG 项目网络。
对任务 :
- :最早开始时间。
- :最早完成时间。
- :任务耗时。
若 没有依赖,则:
若 有依赖,则:
并且:
关键路径含义
关键路径是决定整个项目完成时间的一条任务链。
如果关键路径上的任务延迟,整个项目会延迟;如果减少非关键路径任务时间,通常不能缩短总工期。
项目最短完成时间为:
DFS 计算关键路径
课件第 18-23 页给出 DFS 版本的关键路径算法。
需要维护:
duration[v]:任务时长。eft[v]:最早完成时间。critDep[v]:使 最晚开始的关键依赖。
在 DFS 回溯或检查已完成邻接点时,用依赖任务的 eft 更新当前任务的 est:
如果 让当前最大值更新,则:
最后:
复杂度与 DFS 相同:
考点4:强连通分量
定义
在有向图中,如果一个顶点集合内任意两个顶点都互相可达,并且这个集合不能再扩大,则它是一个强连通分量。
强连通分量缩成点之后得到的凝聚图一定是 DAG。
转置图
10-DAGApp.ppt 第 25 页介绍转置图 。
转置图把原图中每条边方向反过来:
原图和转置图有相同的强连通分量,只是分量之间边的方向反了。
Kosaraju 算法
根据课件第 30-31 页,强连通分量算法分两阶段:
- 在原图 上做 DFS,在每个顶点完成时把它压入栈
finishStack。 - 构造转置图 。
- 按
finishStack的弹出顺序,在 上做 DFS。 - 第二阶段中每棵 DFS 树对应一个强连通分量。
为什么按完成时间逆序
第一阶段完成时间较晚的顶点,会在第二阶段更早被处理。
在转置图上从这样的顶点开始 DFS,可以刚好收集它所在的强连通分量,而不会越过到其它尚未处理的分量。
课件第 33-34 页给出的正确性思路是:
- 每次从栈中弹出的白色顶点,是某个强连通分量的 leader。
- 第二阶段从该 leader 出发的 DFS 树,恰好包含一个完整强连通分量。
算法复杂度为:
11. 贪心法的思想和特点、换硬币和单源最短路径
复习资料
12-Greedy.ppt第 4-7 页、第 25-26 页:贪心思想、换硬币反例、Dijkstra第4章 贪心算法.ppt第 1 页、第 10-12 页、第 27-31 页:贪心基本要素、单源最短路径高级算法期末复习录音.docx2026高级算法复习.pdf
考点1:贪心法的思想和特点
基本思想
贪心算法总是在当前状态下作出看起来最好的选择。
也就是说,它不直接从整体最优出发穷举所有方案,而是一步一步扩展部分解,每一步都选一个局部最优的候选。
12-Greedy.ppt 第 7 页把贪心选择描述为三个要求:
- 可行:选择后仍满足问题约束。
- 局部最优:在当前可行选择中最好。
- 不可撤销:一旦选入,后续不再反悔。
贪心算法框架
贪心算法通常可以写成:
最后如果 构成解,就返回 。
两个基本要素
根据 第4章 贪心算法.ppt 第 10-12 页,能用贪心法求最优解的问题通常具有两个性质:
- 贪心选择性质。
- 最优子结构性质。
贪心选择性质指:整体最优解可以通过一系列局部最优选择得到。
最优子结构性质指:问题的最优解包含其子问题的最优解。
为什么需要证明
贪心算法不总是正确。12-Greedy.ppt 第 6 页给出换硬币反例:
若硬币面值为:
要支付 ,贪心会选择:
共 枚硬币。但最优解是:
只需要 枚硬币。
所以贪心题不能只写算法,还要说明为什么局部最优选择能导向全局最优。
考点2:换硬币
问题描述
给定若干面值的硬币,每种硬币数量足够多,要求用尽可能少的硬币凑出金额 。
常见贪心策略是:
- 每次选择不超过剩余金额的最大面值硬币。
- 从剩余金额中扣除该面值。
- 重复直到剩余金额为 。
贪心不一定适用
对于一般硬币系统,贪心不一定最优。
例如:
贪心得到 ,但最优是 。
因此考试中如果要求证明,必须结合特定硬币系统说明贪心成立。
特殊面值系统
若硬币面值为:
即:
则贪心策略是最优的。
证明思路
在任意最优解中,低一档硬币的数量不能达到 。
原因是:如果有 枚面值为 的硬币,那么它们可以替换成 枚面值为 的硬币:
替换后总金额不变,但硬币数从 枚变成 枚,硬币数更少。这与“最优解使用硬币数最少”矛盾。
所以在最优解中,每个低面值硬币的数量都小于 。
这说明最高面值硬币应尽可能多取,且这个选择与贪心一致。取完最高面值硬币后,剩余金额仍是同类子问题,可以递归证明后续选择也与贪心一致。
考点3:单源最短路径
问题描述
根据 第4章 贪心算法.ppt 第 27 页,单源最短路径问题是:
给定带权有向图:
每条边权为非负实数,并给定源点 。要求计算从 到其他所有顶点的最短路径长度。
Dijkstra 算法适用于:
算法思想
Dijkstra 算法维护一个集合 :
初始时:
并维护数组 dist,其中 dist[v] 表示当前从源点到 的最短特殊路径长度。
每一步执行:
- 从 中选择
dist最小的顶点 。 - 把 加入 。
- 用 的出边更新其他顶点的
dist,即松弛操作。
松弛公式为:
贪心选择为什么成立
每次选择 中 dist 最小的顶点 。
由于所有边权非负,任何从源点绕道其他未确定顶点再到 的路径,都不可能比当前最小的 dist[u] 更短。
因此一旦 被选中,它的最短路径长度就可以确定,之后不会再变小。
这正是 Dijkstra 的贪心选择性质。
复杂度
若用邻接矩阵和简单数组实现,每轮扫描所有未确定顶点找最小 dist,总复杂度通常为:
若用优先队列维护未确定顶点,复杂度取决于堆实现,常见写法为:
其中 ,。
12. 动态规划的思想和经典问题
复习资料
第3章 动态规划.ppt第 1-21 页:动态规划思想、最优子结构、重叠子问题、最长公共子序列13-DynamProg.ppt第 4-9 页:递归、子问题图、动态规划基本思想DynamicProgrammingReview.pdf高级算法期末复习录音.docx2026高级算法复习.pdf
考点1:动态规划的思想和特点
基本思想
动态规划和分治法一样,都会把原问题分解为子问题。
区别在于:动态规划中的子问题通常不是互相独立的,不同递归分支可能反复遇到同一个子问题。
因此,动态规划的核心是:
之后再遇到同一子问题时,直接查表。
两个关键性质
根据 第3章 动态规划.ppt 第 14-15 页,动态规划通常依赖两个性质:
- 最优子结构。
- 重叠子问题。
最优子结构指:原问题的最优解包含子问题的最优解。
重叠子问题指:递归求解时,许多子问题会被反复计算。
解题步骤
动态规划题通常按以下顺序写:
- 定义状态。
- 写状态转移方程。
- 写边界条件。
- 确定填表顺序。
- 描述算法或伪代码。
- 分析时间复杂度和空间复杂度。
子问题图视角
13-DynamProg.ppt 第 5-7 页把子问题看成一个 DAG。
- 顶点表示子问题。
- 边表示一个子问题依赖另一个子问题。
- 动态规划就是按依赖顺序计算这些顶点。
如果按反向拓扑序计算,就可以保证计算某个状态时,它依赖的子状态已经求出。
考点2:最长公共子序列
问题描述
给定两个序列:
要求找出它们的最长公共子序列。
子序列不要求连续,只要求相对顺序保持不变。
状态定义
令:
目标是求:
状态转移
根据 第3章 动态规划.ppt 第 18-20 页:
若:
则这两个字符可以作为公共子序列的最后一个字符:
若:
则最后一个字符不可能同时取二者,只能丢掉一个前缀末尾:
合并写成:
边界和复杂度
当某个序列为空时,最长公共子序列长度为 :
状态数为:
每个状态 计算,因此时间复杂度为:
空间复杂度为:
若只求长度,可以滚动数组优化到:
考点3:抽牌拿取最大值
问题描述
有一排牌:
两名玩家轮流从最左端或最右端取一张牌,双方都采取最优策略。常见问题是求先手最多能获得多少收益,或先手相对后手的最大净胜分。
差值状态
定义:
这里用“当前玩家”而不是固定先手,是为了让子问题保持同一种含义。
状态转移
当前玩家有两个选择:
- 取左端牌 ,之后对手面对 。
- 取右端牌 ,之后对手面对 。
如果当前玩家取左端,则净胜分为:
如果当前玩家取右端,则净胜分为:
所以:
边界和复杂度
当只剩一张牌时:
填表时按区间长度从小到大计算。
状态数量为:
每个状态 转移,因此:
考点4:回文判断和分割问题
回文判断
定义:
若两端字符相同,并且中间部分也是回文,则整体是回文。
状态转移:
其中:
- 表示长度为 、 或 的短串,只要两端相等即可。
- 长串需要继续检查内部子串。
填表顺序通常按子串长度从小到大。
最小回文分割
定义:
如果整个前缀 本身就是回文,则:
否则枚举最后一段回文串的起点 :
完整写法:
最长回文子序列
如果考的是最长回文子序列,可以定义:
若:
则:
若:
则:
边界为:
回文类区间 DP 的共同点是:状态通常定义在区间 上,填表顺序通常按区间长度递增。