数据结构(c++)学习笔记--绪论


一、计算

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} Hailstonen= {1}{n}UHailstone(n/2){n}UHailstone(3n+1)n1n为偶数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

    • TM总是处于有限种状态中的某一种
    • 每经过一个节拍可按照规则转向另一种状态
    • 统一约定,‘h’ = hal
      pSKOM38.png

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/<) //遇到其他状况停止

  • 效果
    pSKjst1.png

3.RAM

3.1 组成

  • 寄存器顺序编号,总数没有限制

  • 可通过编号直接访问任意寄存器

  • 每一基本操作仅需常数时(循环及子程序本身非基本操作)
    pSKjTht.png

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{xdx<=c}=max{xdx<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

  • 效果

    pSKvIrF.png

  • 表的行数即所执行基本指令的总条数

三、渐进复杂度

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 =On3 (当常数趋近于n时)

  • 与T(n)相比,f(n)在形式上更为简洁,但依然反映前者的增长趋势

pSQZxT1.png

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)**

pSQmMU1.png

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(7n215n+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 aknk+ak1nk1+...+a2n2+a1n+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.层次分级

pSQnuQS.png

四、复杂度分析

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(k1)k1=211+231+341+...+(n1)n1=1n1=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) kIsAPerfectPowernk11=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=lnk=1nk=lnn!(n+0.5)lnnn=Θ(nlogn)
  • 对数 + 线性 + 指数: ∑ 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=1nklogk1xlnxdx=[4x2(2lnx1)]1n=Θ(n2logn)

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=nn=O(n2)

pSlKs6f.png

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(n1)=O(n2)

pSlKocV.png

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(n1)=2log2n1=O(n)

pSlKHnU.png

for( int i = 0; i < n; i++ ) 
for( int j = 0; j < i; j += 2022 ) 
O1op( const i, const j )

pSlKjhR.png

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=0nlog2i=O(nlogn)

pSlMPBD.png

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 为求解一个大规模的问题,可以

  • 将其划分为两个子问题:其一平凡,另一规模缩减

  • 分别求解子问题;再由子问题的解,得到原问题的解
    pS3PPWF.png

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)

pS3iQA0.png

2.分而治之

2.1 为求解一个大规模的问题,可以将其划分为若干子问题 (通常两个,且规模大体相当),分别求解子问题,由子问题的解,合并得到原问题的解
pS3iaH1.png
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+logn1)=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)=2T(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(nlogbaalogkn),T(n)=Θ(nlogbaalogk+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)=1T(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)=2T(n/2)+O(n)=O(nlogn)
    • 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)=2T(n/2)+O(nlogn)=O(nlog2n)
  • 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)=1T(n/2)+O(n)=O(n)

六、动态规划

1.记忆法-fib()

1.1 fib(n)=fib(n-1)+fib(n-2)

pS3ErdA.png

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 ϕ36225,ϕ43230109flo=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 ϕ510,ϕ671014flo=105sec1day
  • ϕ 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 ϕ92109flo=1010sec105day3century

1.4 递归版fib()低效的根源在于,各递归实例均被大量地重复调用。先后出现的递归实例,共计O( ϕ n ϕ^n ϕn)个;而去除重复之后,总共不过O(n)种

O( ϕ n ϕ^n ϕn)O(n)
pS3ExeJ.pngpS3V9F1.png

1.5 动态规划

  • 颠倒计算方向: 由自顶而下递归,改为自底而上迭代
f = 1; g = 0; //fib(-1), fib(0) 
while ( 0 < n-- ) { 
    g = g + f; f = g - f; 
} 
return g;

pS3VkQO.png

此时T(n) = O(n),而且仅需O(1)空间

2.最长公共子序列(LCS)

2.1 定义

  • 子序列(Subsequence):由序列中若干字符,按原相对次序构成
    pS3VaYq.png

  • 最长公共子序列(Longest Common Subsequence):两个序列公共子序列中的最长
    pS3Vc79.png

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’
    [pS3V70H.png

  • A[n-1]≠ B[m-1],则在 LCS(n,m-1) 与 LCS(n-1,m) 中取更长者
    pS3VLtI.png

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} (nan+mab)=(mbn+mab);特别地,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 动态规划

  • 采用动态规划的策略 只需O(n+m)时间即可计算出所有子问题

  • 将所有子问题(假想地)列成一张表,颠倒计算方向,从LCS(0,0)出发,依次计算出所有项——直至LCS(n,m)
    pS3ZICq.png

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;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值