文章目录
一、计算
1.算法
1.1 计算 = 信息处理
- 借助某种工具,遵照一定规则,以明确而机械的形式进行
1.2 计算模型 = 计算机 = 信息处理工具
1.3 所谓算法,即特定计算模型下,旨在解决特定问题的指令序列
-
输入-待处理的信息(问题)
-
输出-经处理的信息(答案)
-
正确性-的确可以解决指定的问题
-
确定性-可描述为一个由基本操作组成的序列 (如加盐少许,加糖适量,煮至半熟… )
-
可行性-每一基本操作都可实现,且在常数时间内完成 (如把大象放进冰箱,有三步…)
-
有穷性-对于任何输入,经有穷次基本操作,都可以得到输出
2.有穷性
2.1 希尔顿序列 (Hailstone Sequence)
-
H a i l s t o n e ( n ) = { { 1 } n ≤ 1 { n } U H a i l s t o n e ( n / 2 ) n 为偶数 { n } U H a i l s t o n e ( 3 n + 1 ) n 为奇数 Hailstone(n)=\begin{cases} \{1\} & n≤1 \\ \{n\} U Hailstone(n/2) & n为偶数 \\ \{n\} U Hailstone(3n+1) & n为奇数 \end{cases} Hailstone(n)=⎩ ⎨ ⎧{1}{n}UHailstone(n/2){n}UHailstone(3n+1)n≤1n为偶数n为奇数
-
如Hailstone(42)={42,21, 64, 32, …, 1}
-
总趋势是下降的
2.2 代码
int hailstone(int n){
int length = 1;
while (1 < n) { n % 2 ? n = 3 * n + 1 : n /= 2; length++; }
return length;
}
2.3 但对于希尔顿序列是否全部有穷,仍然未知,所以希尔顿序列不是算法
3.正确的算法
3.1 符合语法,能够编译、链接
-
能够正确处理简单的输入
-
能够正确处理大规模的输入
-
能够正确处理一般性的输入
-
能够正确处理退化的输入
-
能够正确处理任意合法的输入
3.2 健壮
- 能辨别不合法的输入并做适当处理,而不致非正常退出
3.3 可读
- 结构化 + 准确命名 + 注释 + …
3.4 效率
- 速度尽可能快,存储空间尽可能少
二、计算模型
1.统一尺度
1.1 TA§ = 算法A求解问题实例P的计算成本
1.2 同一问题通常有多种算法,实验统计时评估各类算法优劣最直接的方式,但不足以准确反映算法的真正效率
-
不同的算法,可能更适应于不同类型的输入
-
同一算法,可能由不同程序员、用不同程序语言、经不同编译器生成
-
同一算法,可能实现并运行于不同的体系结构、操作系统…
1.3 为了给出客观的评判,需要抽象出一个理想的平台或模型
2.图灵机
2.1 构成部件
-
Tape-依次均匀地划分为单元格,各存有某一字符,初始均为’#
-
Alphabet-字符的种类有限
-
Head
- 总是对准某一单元格,并可读取或改写其中的字符
- 每经过一个节拍,可转向左侧或右侧的邻格
-
State
2.2 转换函数
- Transition Function:(q, c; d, L / R, p)
-
若当前状态为q,且当前字符为c,则将当前字符改写为d,转向左 / 右侧邻格, 转入’p’状态
-
特别地,一旦转入约定的状态’h’,则停机
-
从启动至停机,所经历的节拍数目,即可用以度量计算的成本,亦等于Head累计的移动次数(无量纲)
-
2.3 实例(Increase)
-
功能:将二进制非负整数加一
-
原理:全’1’的后缀,翻转为全’0’,原最低位’0’或’#‘翻转为’1’
-
代码
-
(<, 1; 0, L, <) – 左行,1->0
-
(<, 0; 1, R, >) – 掉头,0->1
-
(<, #; 1, R, >)-- 遇到其他状况,该语句不执行
-
(>, 0; 0, R, >) – 右行
-
(>, #; #, L, h/<) //遇到其他状况停止
-
3.RAM
3.1 组成
3.2 语言
- 赋值操作
-
R\[i]<-c
-
R\[i]<-R\[j]
-
R\[i]<-R\[R\[j]]
-
R\[R\[i]]<-R\[j]
-
R\[i]<-R\[j]+R\[k]
-
R\[i]<-R\[j]-R\[k]
-
- 条件语句
-
IF R\[i]=0 GOTO 1
(如果R[i]=0则跳往语句1) -
IF R\[i]>0 GOTO 1
-
STOP
-
3.3 效率
-
与TM模型一样,RAM模型也是一般计算工具的简化与抽象,使我们可以独立于具体的平台,对算法的效率做出可信的比较与评判
-
算法的运行时间 = 算法需要执行的基本操作次数
-
T(n) = 算法为求解规模为n的问题,所需执行的基本操作次
3.4 实例(Ceiling Division)
-
功能:向下取整的除法,0 <= c,0 < d
⌈ c / d ⌉ = m a x { x ∣ d ∗ x < = c } = m a x { x ∣ d ∗ x < 1 + c } \lceil c/d \rceil=max\{x|d*x<=c\}=max\{x|d*x<1+c\} ⌈c/d⌉=max{x∣d∗x<=c}=max{x∣d∗x<1+c} -
算法:反复地从 R[0] = c 中,减去 R[1] = d,统计在下溢之前,所做减法的次数x
-
代码
-
[0] R[3] <-1
-
[1] GOTO 4
-
[2] R[2] <- R[2] + R[3]
-
[3] R[0] <- R[0] – R[1]
-
[4] IF R[0] > 0 GOTO 2
-
[5] R[0] <-R[2]
-
[6] STOP
-
-
效果
-
表的行数即所执行基本指令的总条数
三、渐进复杂度
1.大O记号
1.1 渐进分析
-
基本操作次数 T(n)
-
存储单元数 S(n)
1.2 Big-O notation
-
T(n)=O(f(n)) iff ∃c>0 当n>>2时,有T(n)<c*f(n)
-
实例: 5 n ⋅ [ 3 n ⋅ ( n + 2 ) + 4 ] + 6 < 5 n ⋅ [ 6 n 2 + 4 ] + 6 < 35 n 3 + 6 < 6 n 3 = O ( n 3 ) \sqrt{5n·[3n·(n+2)+4]+6}<\sqrt{5n·[6n^2+4]+6}<\sqrt{35n^3+6}<6\sqrt{n^3}=O(\sqrt{n^3}) 5n⋅[3n⋅(n+2)+4]+6<5n⋅[6n2+4]+6<35n3+6<6n3=O(n3)(当常数趋近于n时)
-
与T(n)相比,f(n)在形式上更为简洁,但依然反映前者的增长趋势
1.3 T(n)=Ω(f(n)) iff ∃c>0 当n>>2时,有T(n)<c*f(n)
1.4 T(n)=Θ(f(n)) iff ∃ c 1 c_1 c1> c 2 c_2 c2>0 当n>>2时,有** c 1 c_1 c1·f(n)>T(n)> c 2 c_2 c2·f(n)**
2.多项式
2.1 常数O(1)
-
从渐近的角度来看,再大的常数,也要小于递增的变数
-
实例:2 = 2022 = 2022 x 2022 = O(1),即 202 2 2022 2022^{2022} 20222022 = O(1)
-
这类算法的效率最高,即不含转向(循环,调用,递归等),必顺序执行
//循环
for(i=0;i<n;i+=n/2013+1)
for(i=1;i<n;i=1<<i)
//分支转向
if((n+m)*(n+m)<48n8m) goto UNREACHABLE
//(递归)调用
if(2==(n*n)%5) O1(n)
2.2 对数( log b n \log_{}^{b} n logbn)
- 不注明底数原因
-
∀a,b>1, log a n \log_a n logan= log a b \log_a b logab· log b n \log_b n logbn=O( log b n \log_b n logbn)
-
∀c>0, log n c \log n^c lognc=c· log n \log n logn=O( log n \log n logn)
-
123· log 321 n + l o g 205 ( 7 ⋅ n 2 − 15 ⋅ n + 3 ) \log_{}^{321} n + log_{}^{205} (7·n^2-15·n+3) log321n+log205(7⋅n2−15⋅n+3)=O( log 321 n \log_{}^{321} n log321n)
-
- 这类算法非常有效,复杂度无限接近于常数:∀c>0,$\log n$=O($n^c$)
2.3 多项式(O( n c n^c nc))
- a k ⋅ n k + a k − 1 ⋅ n k − 1 + . . . + a 2 ⋅ n 2 + a 1 ⋅ n + a 0 = O ( n K ) , a k > 0 a_k·n^k+a_{k-1}·n^{k-1}+...+a_2·n^2+a_1·n+a_0=O(n^K),a_k>0 ak⋅nk+ak−1⋅nk−1+...+a2⋅n2+a1⋅n+a0=O(nK),ak>0
- 即只取最大的次方,其他皆可无视
2.4 线性
- 指所有O(n)类函数
3.指数(O( 2 n 2^n 2n))
3.1 T ( n ) = O ( a n ) , a > 1 , 即 ∀ c > 1 , n c = O ( 2 n ) T(n)=O(a^n),a>1,即∀c>1,n^c=O(2^n) T(n)=O(an),a>1,即∀c>1,nc=O(2n)
3.2 实例
- n 1000...01 = O ( 1.0000.0 1 n ) = O ( 2 n ) n^{1000...01}=O(1.0000.01^n)=O(2^n) n1000...01=O(1.0000.01n)=O(2n)
- 1.00...0 1 n = Ω ( n 1000...01 ) 1.00...01^n=Ω(n^{1000...01}) 1.00...01n=Ω(n1000...01)
3.3 从 O ( n c ) 到 O ( 2 n ) O(n^c)到O(2^n) O(nc)到O(2n),是从有效算法到无效算法的分水岭
4.层次分级
四、复杂度分析
1.级数
1.1 级数
-
算术级数:与末项平方同阶 T ( n ) = 1 + 2 + . . . + n = n ( n + 1 ) / 2 = O ( n 2 ) T(n)=1+2+...+n=n(n+1)/2=O(n^2) T(n)=1+2+...+n=n(n+1)/2=O(n2)
-
幂方级数:比幂次高出一阶 T ( n ) = 1 2 + 2 2 + . . . + n 2 = n ( n + 1 ) ( 2 n + 1 ) / 2 = O ( n 3 ) T(n)=1^2+2^2+...+n^2=n(n+1)(2n+1)/2=O(n^3) T(n)=12+22+...+n2=n(n+1)(2n+1)/2=O(n3)
-
几何级数:与末项同阶 T a ( n ) = a 0 + a 1 + a 2 + . . . + a n = O ( a n ) T_a(n)=a^0+a^1+a^2+...+a^n=O(a^n) Ta(n)=a0+a1+a2+...+an=O(an),1<a$
1.2 收敛级数
∑ k = 2 n 1 ( k − 1 ) ⋅ k = 1 2 ⋅ 1 + 1 2 ⋅ 3 + 1 3 ⋅ 4 + . . . + 1 ( n − 1 ) ⋅ n = 1 − 1 n = O ( 1 ) \sum_{k=2}^{n}\frac{1}{(k-1)·k}=\frac{1}{2·1}+\frac{1}{2·3}+\frac{1}{3·4}+...+\frac{1}{(n-1)·n}=1-\frac{1}{n}=O(1) ∑k=2n(k−1)⋅k1=2⋅11+2⋅31+3⋅41+...+(n−1)⋅n1=1−n1=O(1)
∑ k I s A P e r f e c t P o w e r n 1 k − 1 = 1 3 + 1 7 + 1 8 + 1 15 + . . . = 1 = O ( 1 ) \sum_{k Is A Perfect Power}^{n}\frac{1}{k-1}=\frac{1}{3}+\frac{1}{7}+\frac{1}{8}+\frac{1}{15}+...=1=O(1) ∑kIsAPerfectPowernk−11=31+71+81+151+...=1=O(1)
- 几何分布: ( 1 − λ ) [ 1 + 2 λ + 3 λ 2 + 4 λ 3 + . . . ] = 1 / ( 1 − λ ) = O ( 1 ) (1-λ)[1+2λ+3λ^2+4λ^3+...]=1/(1-λ)=O(1) (1−λ)[1+2λ+3λ2+4λ3+...]=1/(1−λ)=O(1),0<λ<1
1.3 不收敛,但有限
- 调和级数:h(n)= ∑ k = 1 n 1 k = 1 + 1 2 + 1 3 + . . . + 1 n = l n η + γ + O ( 1 2 n ) = Θ ( log n ) \sum_{k=1}^{n}\frac{1}{k}=1+\frac{1}{2}+\frac{1}{3}+...+\frac{1}{n}=lnη+γ+O(\frac{1}{2n})=Θ(\log n) ∑k=1nk1=1+21+31+...+n1=lnη+γ+O(2n1)=Θ(logn)
- 对数级数: ∑ k = 1 n ln k = ln ∏ k = 1 n k = ln n ! ≈ ( n + 0.5 ) ⋅ ln n − n = Θ ( n ⋅ log n ) \sum_{k=1}^{n}\ln k=\ln\prod_{k=1}^nk=\ln n!≈(n+0.5)·\ln n-n=Θ(n·\log n) ∑k=1nlnk=ln∏k=1nk=lnn!≈(n+0.5)⋅lnn−n=Θ(n⋅logn)
- 对数 + 线性 + 指数: ∑ k = 1 n k ⋅ log k ≈ ∫ 1 ∞ x ⋅ ln x d x = [ x 2 ⋅ ( 2 ⋅ ln x − 1 ) 4 ] 1 n = Θ ( n 2 ⋅ log n ) \sum_{k=1}^{n}k·\log k≈\int_1^\infty x·\ln xdx=[\frac{x^2·(2·\ln x-1)}{4}]_1^n=Θ(n^2·\log n) ∑k=1nk⋅logk≈∫1∞x⋅lnxdx=[4x2⋅(2⋅lnx−1)]1n=Θ(n2⋅logn)
2.迭代
2.1 迭代 + 算术级数
(该代码执行时间与图形面积相等)
for( int i = 0; i < n; i++ )
for( int j = 0; j < n; j++ )
O1op(const i, const j)
∑ k = 1 n n = n ⋅ n = O ( n 2 ) \sum_{k=1}^{n}n=n·n=O(n^2) ∑k=1nn=n⋅n=O(n2)
for( int i = 0; i < n; i++ )
for( int j = 0; j < i; j++ )
O1op(const i, const j)
∑ k = 1 n n = n ⋅ ( n − 1 ) 2 = O ( n 2 ) \sum_{k=1}^{n}n=\frac{n·(n-1)}{2}=O(n^2) ∑k=1nn=2n⋅(n−1)=O(n2)
2.2 迭代 vs 级数
for( int i = 1; i < n; i <<= 1 ) (<<为在二进制中向左移动一位,即乘2)
for( int j = 0; j < i; j++ )
O1op( const i, const j )
1 + 2 + 4 + . . . + 2 log 2 ( n − 1 ) = 2 log 2 n − 1 = O ( n ) 1+2+4+...+2^{\log_2 (n-1)}=2^{\log_2 n}-1=O(n) 1+2+4+...+2log2(n−1)=2log2n−1=O(n)
for( int i = 0; i < n; i++ )
for( int j = 0; j < i; j += 2022 )
O1op( const i, const j )
2.3 迭代 + 复杂级数
for( int i = 0; i <= n; i++ )
for( int j = 1; j < i; j += j )
O1op( const i, const j )
T ( n ) = ∑ i = 0 n ⌈ log 2 i ⌉ = O ( n log n ) T(n)=\sum_{i=0}^{n}\lceil\log_2 i \rceil=O(n\log n) T(n)=∑i=0n⌈log2i⌉=O(nlogn)
3.封底估算
- 1天=24hr x 60min x 60sec≈25 x 4000= 1 0 5 10^5 105sec
- 一生=一世纪=100yr x 365=3 x 1 0 4 10^4 104day=3 x 1 0 9 10^9 109sec
五、迭代与递归
1.减而治之
1.1 为求解一个大规模的问题,可以
1.2 递归跟踪:绘出计算过程中出现过的所有递归实例(及其调用关系)
- 它们各自所需时间之总和,即为整体运行时间
1.3 实例
sum( int A[], int n ) {
return n < 1 ? 0 : sum(A, n - 1) + A[n - 1];
}
总体运行时间为T(n)=O(1) x (n+1)=O(n)
1.4 对于大规模的问题、复杂的递归算法,递归跟踪不再适用此时可采用另一抽象的方法
- 从递推的角度看,为求解规模为n的问题
sum(A, n)
‾
\underline{\text{sum(A, n)}}
sum(A, n)(T(n)),需递归求解规模为n-1的问题
sum(A, n-1)
‾
\underline{\text{sum(A, n-1)}}
sum(A, n-1)(T(n-1)),再累加上
A[n - 1]
‾
\underline{\text{A[n - 1]}}
A[n - 1](O(1))
- 递推方程:T(n)=T(n-1)+O(1),T(0)=O(1)
- 解:T(n)=T(n+2)+O(2)=T(n-3)+O(3)=…=T(0)+O(n)=O(n)
void reverse( int * A, int lo, int hi )
(将数组中的区间A[lo,hi]前后颠倒)
//减治
Rev(lo,hi)=[hi]+Rev(lo+1,hi-1)=[lo]
//递归:
if (lo < hi) {
swap( A[lo], A[hi] );
reverse( A, lo + 1, hi – 1 );
}//线性递归(尾递归),O(n)
//迭代
while (lo < hi) swap( A[lo++], A[hi--] ); //亦是O(n)
2.分而治之
2.1 为求解一个大规模的问题,可以将其划分为若干子问题 (通常两个,且规模大体相当),分别求解子问题,由子问题的解,合并得到原问题的解
T
(
n
)
=
各层递归实例所需时间之和(递归跟踪)
=
O
(
1
)
x
(
2
0
+
2
1
+
2
2
+
.
.
.
+
2
log
b
=
n
)
=
O
(
1
)
x
(
2
1
+
log
n
−
1
)
=
O
(
n
)
\begin{aligned} T(n)&=各层递归实例所需时间之和(递归跟踪)\\ &=O(1) x (2^0+2^1+2^2+...+2^{\log b=n}) \\ &=O(1) x (2^{1+\log n}-1) \\ &=O(n) \end{aligned}
T(n)=各层递归实例所需时间之和(递归跟踪)=O(1)x(20+21+22+...+2logb=n)=O(1)x(21+logn−1)=O(n)
2.2 实例
sum( int A[], int lo, int hi ) {
if ( hi - lo < 2 ) return A[lo];
int mi = (lo + hi) >> 1; return sum( A, lo, mi ) + sum( A, mi, hi );
}
2.3 从递推的角度看,为求解sum(A, lo, hi),需要递归求解 sum(A, lo, mi) ‾ \underline{\text{sum(A, lo, mi)}} sum(A, lo, mi)和 sum(A, mi+1, hi) ‾ \underline{\text{sum(A, mi+1, hi)}} sum(A, mi+1, hi)(2*T(n/2)),进而将子问题的解累加(O(1))
- 递推方程:
- T(n)=2·T(n/2)+O(1)
- T(1)=O(1)
- 解:T(n)=4·T(n/4)+O(3)=8·T(n/8)+O(7)=16·T(n/16)+O(15)=…=n·T(1)+O(n-1)=O(n)
2.4 Master Theorem
-
分治策略对应的递推式,通常(尽管不总是)形如:T(n)=a·T(n/b)+O(f(n))(原问题被分为a个规模均为n/b的子任务;任务的划分、解的合并总共耗时f(n))
-
若 f ( n ) = O ( n log b a a − ε ) , 则 T ( n ) = Θ ( n log b a a ) f(n)=O(n^{\log_{b}^{a}a-ε}),则T(n)=Θ(n^{\log_{b}^{a}a}) f(n)=O(nlogbaa−ε),则T(n)=Θ(nlogbaa)
- 实例: T ( n ) = 2 ⋅ T ( n / 4 ) + O ( 1 ) = O ( n ) T(n)=2·T(n/4)+O(1)=O(\sqrt{n}) T(n)=2⋅T(n/4)+O(1)=O(n)
-
若 f ( n ) = O ( n log b a a ⋅ log k n ) , 则 T ( n ) = Θ ( n log b a a ⋅ log k + 1 n ) f(n)=O(n^{\log_{b}^{a}a}·\log_{}^{k}n),则T(n)=Θ(n^{\log_{b}^{a}a}·\log_{}^{k+1}n) f(n)=O(nlogbaa⋅logkn),则T(n)=Θ(nlogbaa⋅logk+1n)
- 实例: T ( n ) = 1 ⋅ T ( n / 2 ) + O ( 1 ) = O ( log n ) T(n)=1·T(n/2)+O(1)=O(\log n) T(n)=1⋅T(n/2)+O(1)=O(logn)
- T ( n ) = 2 ⋅ T ( n / 2 ) + O ( n ) = O ( n ⋅ log n ) T(n)=2·T(n/2)+O(n)=O(n·\log n) T(n)=2⋅T(n/2)+O(n)=O(n⋅logn)
- T ( n ) = 2 ⋅ T ( n / 2 ) + O ( n ⋅ log n ) = O ( n ⋅ log 2 n ) T(n)=2·T(n/2)+O(n·\log n)=O(n·\log_{}^{2} n) T(n)=2⋅T(n/2)+O(n⋅logn)=O(n⋅log2n)
-
若 f ( n ) = Ω ( n log b a a + ε ) , 则 T ( n ) = Θ ( f ( n ) ) f(n)=Ω(n^{\log_{b}^{a}a+ε}),则T(n)=Θ(f(n)) f(n)=Ω(nlogbaa+ε),则T(n)=Θ(f(n))
- 实例: T ( n ) = 1 ⋅ T ( n / 2 ) + O ( n ) = O ( n ) T(n)=1·T(n/2)+O(n)=O(n) T(n)=1⋅T(n/2)+O(n)=O(n)
六、动态规划
1.记忆法-fib()
1.1 fib(n)=fib(n-1)+fib(n-2)
int fib(n) {
return (2 > n) ? n : fib(n - 1) + fib(n - 2);
}
1.2 复杂度:
T(0)=T(1)=1;T(n)=T(n-1)+T(n-2)+1,∀n>1;
令S(n)=[T(n)+1]/2,则S(0)=1=fib(1),S(1)=1=fib(2)
故S(n)=S(n-1)+S(n-2)=fib(n+1),T(n)=2·S(n)-1=2·fib(n+1)-1=O(fib(n+1))=O( ϕ 2 ϕ^2 ϕ2)(ϕ=(1+ 5 \sqrt{5} 5)/2≈1.618->黄金分割率)
1.3 封底估算
- ϕ 36 ≈ 2 25 , ϕ 43 ≈ 2 30 ≈ 1 0 9 f l o = 1 s e c ϕ^{36}≈2^{25},ϕ^{43}≈2^{30}≈10^9flo=1sec ϕ36≈225,ϕ43≈230≈109flo=1sec
- ϕ 5 ≈ 10 , ϕ 67 ≈ 1 0 14 f l o = 1 0 5 s e c ≈ 1 d a y ϕ^5≈10,ϕ^{67}≈10^{14}flo=10^5sec≈1day ϕ5≈10,ϕ67≈1014flo=105sec≈1day
- ϕ 92 ≈ 1 0 9 f l o = 1 0 10 s e c ≈ 1 0 5 d a y ≈ 3 c e n t u r y ϕ^{92}≈10^9flo=10^{10}sec≈10^5day≈3century ϕ92≈109flo=1010sec≈105day≈3century
1.4 递归版fib()低效的根源在于,各递归实例均被大量地重复调用。先后出现的递归实例,共计O( ϕ n ϕ^n ϕn)个;而去除重复之后,总共不过O(n)种
O( ϕ n ϕ^n ϕn) | O(n) |
---|---|
1.5 动态规划
- 颠倒计算方向: 由自顶而下递归,改为自底而上迭代
f = 1; g = 0; //fib(-1), fib(0)
while ( 0 < n-- ) {
g = g + f; f = g - f;
}
return g;
此时T(n) = O(n),而且仅需O(1)空间
2.最长公共子序列(LCS)
2.1 定义
2.2 **递归 **
对于序列A[0,n)和B[0,m),LCS(n,m)无非三种情况
-
若 n = 0 或 m = 0,则取作空序列(长度为零)
-
若A[n-1] = ‘X’ = B[m-1],则取作:LCS(n-1,m-1) + ‘X’
[
unsigned int lcs( char const * A, int n, char const * B, int m ) {
if (n < 1 || m < 1) return 0;
else if ( A[n-1] == B[m-1] ) return 1 + lcs(A, n-1, B, m-1);
else return max( lcs(A, n-1, B, m), lcs(A, n, B, m-1) )
}
2.3 复杂度:每经一次比对,至少一个序列的长度缩短一个单位
-
最好情况,只需O(n+m)时间
-
然而最坏情况下,子问题数量不仅会增加,且可能大量雷同子任务LCS(A[a],B[b])重复的次数,可能多达为 ( n + m − a − b n − a ) = ( n + m − a − b m − b ) \binom{n+m-a-b}{n-a}=\binom{n+m-a-b}{m-b} (n−an+m−a−b)=(m−bn+m−a−b);特别地,LCS(A[0], B[0])的次数可多达 ( n + m n ) = ( n + m m ) \binom{n+m}{n}=\binom{n+m}{m} (nn+m)=(mn+m)
-
当n = m时,为Ω( 2 n 2^n 2n)
unsigned int lcsMemo(char const* A, int n, char const* B, int m) {
unsigned int * lcs = new unsigned int[n*m];
memset(lcs, 0xFF, sizeof(unsigned int)*n*m);
unsigned int solu = lcsM(A, n, B, m, lcs, m);
delete[] lcs;
return solu;
}
unsigned int lcsM( char const * A, int n, char const * B, int m, unsigned int * const lcs, int const M ) {
if (n < 1 || m < 1) return 0;
if (UINT_MAX != lcs[(n-1)*M + m-1]) return lcs[(n-1)*M + m-1];
else return lcs[(n-1)*M + m-1] = (A[n-1] == B[m-1]) ? 1 + lcsM(A, n-1, B, m-1, lcs, M) max( lcsM(A, n-1, B, m, lcs, M), lcsM(A, n, B, m-1, lcs, M) );
}
2.4 动态规划
unsigned int lcs(char const * A, int n, char const * B, int m) {
if (n < m) { swap(A, B); swap(n, m); }
unsigned int* lcs1 = new unsigned int[m+1];
unsigned int* lcs2 = new unsigned int[m+1];
memset(lcs1, 0x00, sizeof(unsigned int) * (m+1));
memset(lcs2, 0x00, sizeof(unsigned int) * (m+1));
for (int i = 0; i < n; swap(lcs1, lcs2), i++)
for (int j = 0; j < m; j++)
lcs2[j+1] = (A[i] == B[j]) ? 1 + lcs1[j] : max(lcs2[j], lcs1[j+1]);
unsigned int solu = lcs1[m];
delete[] lcs1;
delete[] lcs2;
return solu;
}