外观
03 减治与变治算法
本章对应考试中的:V1 多数元素(
2011影印等,5 分)、V2 减治求( 2020,4 分)、V3 减治求数组最小元素位置(HW1)、简答 Q1(二叉查找树属减治哪种变种、最差效率)、简答 Q2(AVL 树与 2-3 树的目的与效率)、作业 O1(约瑟夫斯问题非递推公式)。
核心考点
减治(decrease and conquer) 和 变治(transform and conquer) 是两类"把问题变简单再解"的策略,常考概念辨析(简答题)和具体算法设计(V1~V3)。
- 减治:把问题规模缩小一截,通常只解其中一个子问题(这是它与分治的关键区别——分治要解多个子问题)。
- 变治:先把输入或问题变个形式(排序、换数据结构、转成另一个问题),在新形式上更好解。
本章要能:说清减治/变治各自的几种变体;用变治法把多数元素从
从零开始的前置知识
减治的三种变体
减治 按"每次规模缩小多少"分三种:
- 减常数(decrease by a constant):每次规模减去一个固定常数(通常减 1)。例:求
时 ,规模每次减 1;约瑟夫斯问题每出局一人规模减 1。 - 减常数因子(decrease by a constant factor):每次规模除以一个固定常数(通常除以 2)。例:二分查找每次范围减半;求
每次把 除以 2。 - 减可变规模(variable-size decrease):每次缩小的量不固定。例:二叉查找树的查找(每步往左或右子树走,但两边子树大小可能很不一样);求最大公约数的欧几里得算法。
变治的三种变体
变治 按"变什么"分三种:
- 实例化简(instance simplification):把输入预处理成同类型但更好处理的实例。最常见的是预排序(presorting)——先排序再求解。多数元素的变治法一就是这种。
- 改变表示(representation change):用另一种数据结构表示同样的数据。例:把普通二叉查找树改成 AVL 树 或 2-3 树 以保持平衡。
- 问题化简(problem reduction):把要解的问题转化成另一个已经知道怎么解的问题。例:求两数最小公倍数,转化为求最大公约数。
二分查找:减常数因子的样板
二分查找(binary search):在一个已排好序的数组里找目标值 01_数学基础_渐进记号与递推式求解.md)。
逐题精解
V1 多数元素(2011影印 等,5 分)
原题:
为整数序列,若整数 在 中出现次数多于 ,则 称为多数元素。例如 中 3 是多数元素(出现 4 次 )。问蛮力算法复杂性如何?设计一个具有变治思想的算法提高效率,写出伪代码并分析时间复杂性。
蛮力法:对每个元素,数它在整个数组里出现多少次,看是否超过
变治法一:预排序,
text
Sort(A, n) // 排序,O(n log n)
i ← 0
while i < n:
val ← A[i]; count ← 1
while i+count < n and A[i+count] == val: count++
if count > n/2: return val
i ← i + count
return NONE // 不存在多数元素复杂度由排序主导,
变治法二:Boyer–Moore 摩尔投票,count++,遇到不同的就 count--(相当于一个候选和一个非候选相互抵消);count 减到 0 就换当前元素当新候选。因为多数元素比其它所有元素加起来还多,最后剩下的候选一定是它。
text
cand ← A[0]; count ← 0
for i ← 0 to n-1:
if count == 0: cand ← A[i]; count ← 1
elif A[i] == cand: count++
else: count-- // 一对不同元素相互抵消
return cand // 如需确认,再扫一遍数它是否真的 > n/2演示(
| 读到 | 1 | 3 | 2 | 3 | 3 | 4 | 3 |
|---|---|---|---|---|---|---|---|
| 操作 | count0→候选1 | 3≠1,count0 | count0→候选2 | 3≠2,count0 | count0→候选3 | 4≠3,count0 | count0→候选3 |
| 1 | 1 | 2 | 2 | 3 | 3 | 3 | |
| 1 | 0 | 1 | 0 | 1 | 0 | 1 |
最终候选 3,再扫一遍确认 3 出现 4 次
复杂度 试卷7 里说"Hash 不行"指的是空间不如摩尔投票省)。
V2 减治求 (2020,4 分)
原题:写一个具有减治思想的求
的算法,并推导其时间复杂性。
前置:
思路(减常数因子):不断把
text
DecLog2(N):
k ← 0
while N > 1:
N ← N / 2 // 整数除法(规模减半,减治)
k ← k + 1
return k // 即 ⌊log2 N⌋演示(
复杂度:
V3 减治求数组最小元素的位置(HW1)
原题:对求
个实数数组中最小元素位置的问题,写出具有减治思想算法的伪代码,确定时间效率,并与蛮力算法比较。
思路:把数组对半分,分别求两半的最小元素及其下标,再取较小者对应的下标。
text
(element, index) = FindLeast(A, low, high):
if low == high: return (A[low], low) // 出口
mid ← (low + high) / 2
(e1, i1) = FindLeast(A, low, mid)
(e2, i2) = FindLeast(A, mid+1, high)
return (e1 < e2) ? (e1, i1) : (e2, i2)复杂度:
这道题的真正用意:虽然同为
简答 Q1:二叉查找树属减治哪种变种、最差效率(2011影印 等)
原题:二叉查找树属于减治策略三个变种中哪一个的应用?什么情况下表现出最差效率?此时查找和插入算法的复杂性如何?
前置:什么是二叉查找树。 二叉查找树(binary search tree, BST) 是一种二叉树,满足:任一结点的左子树所有值都比它小、右子树所有值都比它大。查找时从根开始,比当前结点小就往左、大就往右,类似二分查找。
答案:
- 二叉查找树属于减治策略中**"减去的规模可变(减可变规模)"** 变种的应用——每步往左或右子树走,但左右子树的大小可能很不一样。
- 当树严格歪斜、退化成一条链时表现最差(例如把已经有序的序列依次插入,每个新值都比前一个大,全部挂到右边,树变成一条"竖线")。
- 此时查找和插入在最坏情况下的时间复杂度都是
(要从头走到尾,跟在数组里逐个找一样慢)。
简答 Q2:AVL 树与 2-3 树的目的与效率(2011影印 等,2 分)
原题:构造 AVL 树和 2-3 树的主要目的是什么?它们各自的查找和插入效率如何?
前置:上面说过普通 BST 会退化成链导致
- AVL 树:一种自平衡二叉查找树,规定任一结点的左右子树高度差不超过 1,靠"旋转"操作维持平衡。
- 2-3 树:每个结点可以有 2 个或 3 个孩子,所有叶子在同一层,天然平衡。
答案:构造它们的主要目的是维持树的平衡、避免树退化成链,从而压低树高,使查找与插入更快。AVL 树的查找、插入效率均为
作业 O1:约瑟夫斯问题非递推公式(HW1)
原题:给出约瑟夫斯问题的非递推公式
并证明。 为最初总人数, 为最后幸存者的最初编号(每报到 2 出局,即 )。
前置:约瑟夫斯问题。
递推关系(减一治的思想,每出局一人规模减小):
非递推闭式:把
怎么套用:先找出不超过
演示(
等价说法:把
证明思路(数学归纳法):对
推广(每报
高频陷阱 / 易错点小结
- 减治 vs 分治:减治通常只解一个子问题,分治解多个。二分查找是减治,归并排序是分治。
- BST 退化:按有序序列插入会退化成链,最坏
;这是 Q1 的考点。 - 多数元素摩尔投票最后要再扫一遍验证候选是否真超过
(若题目不保证一定存在多数元素)。 - 多数元素的"变治"体现在哪:法一是预排序(实例化简),法二可视为对问题的巧妙变形;答题时点明"变治思想"。
- 约瑟夫斯记住
,别把 找错(要"不超过 的最大 2 的幂")。
自测清单
- [ ] 说出减治三变体(减常数 / 减常数因子 / 减可变规模)各一个例子。
- [ ] 说出变治三变体(实例化简 / 改变表示 / 问题化简)各一个例子。
- [ ] 写出多数元素的摩尔投票伪代码,对
跑出候选 3。 - [ ] 写出求
的减治算法,对 得 4。 - [ ] 答出 BST 属"减可变规模"、退化时查找/插入
。 - [ ] 答出 AVL、2-3 树目的(保持平衡)与效率
。 - [ ] 写出约瑟夫斯闭式
( ),并算 。