考试预测和复习建议
- 第1题:函数的阶:证明
- 第2题:Master定理
- 第3题:分治法
- 第4题:动态规划:压轴题预备
- 第5题:贪心算法
- 第6题:搜索算法:A*
- 第7题:平摊分析
- 第8题:网络流(余图、增广、最大流、最小割)
- 第9题:最短路/最小生成树
- 第10题:字符串匹配算法
大题:思想 + 伪代码 + 时间复杂度 + 证明
- 算法思想 + 伪代码:3-6、8-10必考
- 时间复杂度:3、4、5必考
- 证明:4、5必考
略
渐进紧界 / 常数差异
Θ ( g ( n ) ) = { f ( n ) ∣ ∃ c 1 , c 2 > 0 , n 0 , ∀ n > n 0 , c 1 g ( n ) ≤ f ( n ) ≤ c 2 g ( n ) } \Theta(g(n))=\{f(n) | \exists c_1, c_2>0, n_0, \forall n>n0, c_1g(n)\le f(n)\le c_2g(n)\} Θ(g(n))={ f(n)∣∃c1,c2>0,n0,∀n>n0,c1g(n)≤f(n)≤c2g(n)}
渐进上界 / 最坏情况 / 上限
O ( g ( n ) ) = { f ( n ) : 存 在 正 常 数 c 和 n 0 满 足 对 于 所 有 n ≥ n 0 , 0 ≤ f ( n ) ≤ c g ( n ) } O(g(n))=\{f(n): 存在正常数c和n_0 满足对于所有n\ge n_0, 0\le f(n) \le cg(n)\} O(g(n))={ f(n):存在正常数c和n0满足对于所有n≥n0,0≤f(n)≤cg(n)}
渐进下界 / 最好情况 / 下限
Ω ( g ( n ) ) = { f ( n ) : 存 在 正 常 数 c 和 n 0 满 足 对 于 所 有 n ≥ n 0 , 0 ≤ c g ( n ) ≤ f ( n ) } \Omega (g(n))=\{f(n): 存在正常数c和n_0 满足对于所有n\ge n_0, 0 \le cg(n)\le f(n) \} Ω(g(n))={ f(n):存在正常数c和n0满足对于所有n≥n0,0≤cg(n)≤f(n)}
低阶 + 任意常数均满足
o ( g ( n ) ) = { f ( n ) : ∀ 正 常 数 c , ∃ n 0 满 足 ∀ n ≥ n 0 , 0 ≤ f ( n ) ≤ c g ( n ) } o(g(n))=\{f(n): \forall 正常数c, \exists n_0 满足\forall n\ge n0, 0\le f(n) \le cg(n)\} o(g(n))={ f(n):∀正常数c,∃n0满足∀n≥n0,0≤f(n)≤cg(n)}
高阶 + 任意常数均满足
w ( g ( n ) ) = { f ( n ) : ∀ 正 常 数 c , ∃ n 0 满 足 ∀ n ≥ n 0 , 0 ≤ c g ( n ) ≤ f ( n ) } w(g(n))=\{f(n): \forall 正常数c, \exists n_0 满足\forall n\ge n0, 0 \le cg(n)\le f(n) \} w(g(n))={ f(n):∀正常数c,∃n0满足∀n≥n0,0≤cg(n)≤f(n)}
要证 f ( n ) + o ( f ( n ) ) = Θ ( f ( n ) ) f(n)+o(f(n))=Θ(f(n)) f(n)+o(f(n))=Θ(f(n)),只需证 ∃ c 1 , c 2 , n 0 \exists c_1, c_2, n_0 ∃c1,c2,n0,对于 ∀ n > n 0 , c 1 f ( n ) ≤ f ( n ) + o ( f ( n ) ) ≤ c 2 f ( n ) \forall n>n_0, c_1f(n)\le f(n)+o(f(n))\le c_2f(n) ∀n>n0,c1f(n)≤f(n)+o(f(n))≤c2f(n)
令 g ( n ) = o ( f ( n ) ) g(n)=o(f(n)) g(n)=o(f(n)),有 ∃ c 0 \exists c_0 ∃c0,对于 ∀ n > n 0 , 0 ≤ g ( n ) ≤ c 0 f ( n ) \forall n>n_0,0\le g(n)\le c_0f(n) ∀n>n0,0≤g(n)≤c0f(n),推得 f ( n ) ≤ f ( n ) + g ( n ) ≤ f ( n ) + c 0 f ( n ) f(n)\le f(n)+g(n)\le f(n)+c_0f(n) f(n)≤f(n)+g(n)≤f(n)+c0f(n)
使 c 1 = 1 , c 2 = c 0 + 1 c_1=1, c_2=c_0+1 c1=1,c2=c0+1,得 c 1 f ( n ) ≤ f ( n ) + g f ( n ) ≤ c 2 f ( n ) c_1f(n)\le f(n)+gf(n)\le c_2f(n) c1f(n)≤f(n)+gf(n)≤c2f(n),得证。
求解方程 T ( n ) = a T ( n / b ) + f ( n ) T(n)=aT(n/b)+f(n) T(n)=aT(n/b)+f(n)
其中 a ≥ 1 , b > 0 a\ge1, b>0 a≥1,b>0是常数, f ( n ) f(n) f(n)是正函数
使用 f ( n ) f(n) f(n)与 n log b a n^{\log_{b}a} nlogba比较
步骤:划分、递归、合并
时间复杂性: T ( n ) = a T ( n / b ) + D ( n ) + C ( n ) T(n)=aT(n/b)+D(n)+C(n) T(n)=aT(n/b)+D(n)+C(n)
快速幂
O(logN)求斐波那契数列第N项
对于平面上的两个点p1=(x1, y1)和p2=(x2,y2),如果x1<=x2且y1<=y2,则p2支配p1,给定平面上的n个点,请设计算法求其中没有被任何其他点支配的点
逆序对数求解:有长度为N的浮点数组A,元素分别为a1, a2, …, aN。如果满足i
解法:
- 从ai到aj的逆序对个数 = 从ai到ak中的 + 从ak到aj中的 + 两部分之间的
- 分治:二分计算每段的逆序对个数
- 重点:如何计算两部分之间的——模拟两个有序数列的合并可以计算
- 例:1 4 6 / 2 3 5
- 二分
- 2 放入,经过4和6,两个;
- 3 放入,经过4和6,两个;
- 6放入,经过6,一个;
- 一共5个逆序对,时间 O ( n ) O(n) O(n)
解法:
- 点对数 = 子节点为根的树的点对数 + 子结点为根的树之间的点对树
- 每个节点保存以自己为根的树的点对数
- 不同子树之间,求每个点和当前根的距离
- 每个子树获得一个“距离数列”,不同数列之间寻找数对之和小于K
解法:
- 求两个数组的A[n/2];
- 若相等,中位数就是A[n/2]
- 若A[n/2]>B[n/2],说明中位数在B[n/2: n]和A[1: n/2]里,递归;
- 反之同理
快速傅里叶变化*
Divide-and-conquer算法*
问题描述:输入为x1, x2, …, xn, 子问题是x1, x2, …,xi,子问题复杂性为O(n)
表示状态的方法:
- 状态i表示前i个元素构成的最优解,可能不包含第i个元素。
- 状态i表示在必须包含第i个元素的情况下前i个元素构成的最优解
最大不下降子序列问题
优化:
上面的算法的时间复杂度为O(n2)
分析:设Ai = min { aj | dp[j] == i},那么如果i > j,一定可以推出Ai ≥ Aj。
递推函数:dp[i] = max { j | Aj > ai} + 1; max{j | Aj ≥ ai}可以通过二分查找求出
问题描述:输入为x1, x2, …, xn, 子问题为xi, xi+1,…, xj,子问题复杂性是O(n2)
解法:划分成两部分,取两部分分别的代价与两部分合并的代价之和为总代价
矩阵链乘问题
m [ i , j ] = 0 , i f : i = j m[i, j]= 0, if: i=j m[i,j]=0,if:i=j
m [ i , j ] = m i n i ≤ k < j { m [ i , k ] + m [ k + 1 , j ] + p i − 1 p k p j } , i f : i < j m[i, j]= min_{i\le km[i,j]=mini≤k<j{ m[i,k]+m[k+1,j]+pi−1pkpj},if:i<j
三角剖分
t [ i , i ] = t [ j , j ] = 0 t[i, i] = t[j, j] = 0 t[i,i]=t[j,j]=0
t [ i , j ] = m i n i ≤ k < j { t [ i , k ] + t [ k + 1 , j ] + w } , i f : i < j t[i, j] = min_{i\le kt[i,j]=mini≤k<j{ t[i,k]+t[k+1,j]+w},if:i<j
t [ i , k ] = < v i − 1 , v i , . . . . . , v k > t[i, k] =t[i,k]=<vi−1,vi,.....,vk>的优化三角剖分代价
t [ k + 1 , j ] = < v k , v i , . . . . . , v j > t[k+1, j] =t[k+1,j]=<vk,vi,.....,vj>的优化三角剖分代价
问题描述:输入为x1, x2, …, xn和数字C,子问题为x1, x2, …, xi, K(K≤C),子问题复杂性O(nC)
等价的整数规划问题
m a x Σ 1 ≤ i ≤ n v i x i max \Sigma_{1\le i\le n}v_i x_i maxΣ1≤i≤nvixi
Σ 1 ≤ i ≤ n w i x i ≤ C \Sigma_{1\le i\le n} w_ix_i\le C Σ1≤i≤nwixi≤C
x i ∈ { 0 , 1 } , 1 ≤ i ≤ n x_i\in \{0, 1\}, 1\le i \le n xi∈{ 0,1},1≤i≤n
0-1背包问题
m ( i , j ) = m ( i + 1 , j ) 0 ≤ j < w i m(i, j) = m(i+1, j) 0\le j < wi m(i,j)=m(i+1,j)0≤j<wi
m ( i , j ) = m a x { m ( i + 1 , j ) , m ( i + 1 , j − w i ) + v i } j ≤ w i m(i, j) = max\{m(i+1, j), m(i+1, j-wi)+vi\} j \le wi m(i,j)=max{ m(i+1,j),m(i+1,j−wi)+vi}j≤wi
m ( n , j ) = 0 ; 0 ≤ j < w n m(n, j) = 0; 0 \le jm(n,j)=0;0≤j<wn
m ( n , j ) = v n ; j ≤ w n m(n, j) = v_n;j \le wn m(n,j)=vn;j≤wn
问题描述:输入为x1, x2, …, xn和y1, y2, …, ym,子问题为x1, x2, …, xi和y1, y2, …, yj,子问题复杂性是O(mn)
最长公共子序列问题(编辑距离)
问题描述:输入是树,其子问题为子树,子问题复杂性是子树的个数。
树中独立集合问题
最优二分搜索树
任意两点最短路径问题
若一个优化问题的全局优化解可以通过局部优化选择得到, 则该问题称为具有贪心选择性
若一个优化问题的优化解包含它的子问题的优化解, 则称其具有优化子结构
可用贪心方法时, 动态规划方法可能不适用
可用动态规划方法时, 贪心方法可能不适用
任务安排问题
哈夫曼编码问题
最小生成树问题
样题第5题
拟阵*
略
略
人员安排问题
A*算法与分支界限策略的比较
- 分支界限策略是为了剪掉不能达到优化解的分支,关键是“界限”
- A*算法的核心是告诉我们在某些情况下, 我们得到的解一定是优化解, 于是算法可以停止,试图尽早地发现优化解,经常使用Best-first策略求解优化问题
- 例题见2020算法设计与分析官方考前模拟卷第10题
在平摊分析中,执行一系列数据结构操作所需要时间是通过对执行的所有操作求平均而得出的
实际代价 | 平摊代价 | |
---|---|---|
PUSH | 1 | 2 |
POP | 1 | 0 |
MULTIPOP | m i n ( k , s ) min(k, s) min(k,s) | 0 |
例:二进计数器
显然:这个操作序列的代价与0-1或者1-0翻转发生的次数成正比
任何操作序列,存款余额是计数器中1的个数,非负
因此,所有的翻转操作的平摊代价的和是这个操作序列代价的上界
如果我们将这些余额都与整个数据结构关联,所有的这样的余额之和,构成——数据结构的势能
势能的定义:
例1:栈操作
平摊代价为栈内个数的变化值
例2:二进计数器
平摊代价为计数器中1的个数
如果我们执行了至少n=φ(k)次INCREMENT操作,则无论计数器中包含什么样的初始值,总的实际代价都是O(n)
略
优化子结构:最短路径包含最短子路径
证明: 如果某条子路径不是最短子路径,必然存在最短子路径,用最短子路径替换当前子路径,当前路径不是最短路径,矛盾。
最短路径算法的核心技术是松弛
Relax(u,v,w) {
if (d[v] > d[u]+w) then d[v]=d[u]+w;
}
时间复杂度 O ( V E ) O(VE) O(VE)
BellmanFord()
// 初始化 d
for each v in V
d[v] = MAXINT;
d[s] = 0;
// 松弛: 进行 |V|-1 轮, 松弛每条边
for i=1 to |V|-1
for each edge (u,v) in E
Relax(u,v, w(u,v));
// 检验结果
for each edge (u,v) in E
if (d[v] > d[u] + w(u,v))
return “no solution”;
Dijkstra(G)
// 初始化 d
for each v in V
d[v] = MAXINT;
d[s] = 0;
S = 空集;
Q = V;
while (Q ≠ 空集)
u = ExtractMin(Q);
S = S U {u};
for each v in u->Adj[]
// 松弛
if (d[v] > d[u]+w(u,v))
d[v] = d[u]+w(u,v);
D0 = W //初始化D
P = 0 // 初始化 P
for k = 1 to n
for i = 1 to n
for j = 1 to n
if (Dk-1[ i, j ] > Dk-1 [ i, k ] + Dk-1 [ k, j ] ) then
Dk[ i, j ] = Dk-1 [ i, k ] + Dk-1 [ k, j ]
P[ i, j ] = k;
else Dk[ i, j ] = Dk-1 [ i, j ]
使用最大流算法
余图: G f G_f Gf
给定图G中的流f, 及其对应的余图Gf
沿着P增广f :
- 找到路径的最小容量
- 沿着路径修改权重
FF算法可以求得最大流
v a l u e ( f ) = f o u t ( A ) − f i n ( A ) value(f) = f_{out}(A) - f_{in}(A) value(f)=fout(A)−fin(A)
保证 M i n − c u t ≤ v a l u e ( f ) Min-cut \le value(f) Min−cut≤value(f)
设FF-算法在流f上停止:
从s出发的 DFS 不包含 t,这意味着在余图中DFS经过的结点和其余结点之间割的容量是0
因此这个割中的每条边都被增广流逆转过,这意味着c(DFS,DFS’) = value(f)
根据 M i n − c u t ≤ v a l u e ( f ) Min-cut \le value(f) Min−cut≤value(f)可得到f最大
时间复杂度 O ( n m ) O(nm) O(nm)
略
令字母表位 S={0,1,2,3,4,5,6,7,8,9}
令指纹为一个十进制数, 即, f(“1045”) = 1*103 + 0*102 + 4*101 + 5 = 1045
Fingerprint-Search(T,P)
fp = compute f(P)
f = compute f(T[0..m–1])
for s = 0 to n – m do
if fp = f return s
f = (f – T[s]*10m-1)*10 + T[s+m]
return -1
指纹算法前提:对m位数在O(1)时间内进行算术运算
若不可行,考虑Hash函数
Rabin-Karp-Search(T,P)
q = a prime larger than m
c = 10m-1 mod q // run a loop multiplying by 10 mod q
fp = 0; ft = 0
for i = 0 to m-1 // preprocessing
fp = (10*fp + P[i]) mod q
ft = (10*ft + T[i]) mod q
for s = 0 to n – m // matching
if fp = ft then // run a loop to compare strings
if P[0..m-1] = T[s..s+m-1] return s
ft =((ft – T[s]*c)*10 + T[s+m]) mod q
return –1
网上讲解太多了,搬运一下吧
紧缩Trie树
一种包含文本所有后缀的紧缩trie树(或类似的结构)