动态规划

动态规划

1. 概念
动态规划被广泛地用于求解组合最优化问题,它并不是递归地调用自身,虽然问题的基础解通常是用递归函数的形式来说明的。与分治算法的设计思路不同,动态规划采取自底向上的方式递推求值,并把中间结果存储起来以便以后用来计算所需要求的解。
因此,使用动态规划算法可以高效解决许多组合最优化问题,也可以改善暴力搜索算法的时间复杂度。下面我们根据一个例子来简单了解一下动态规划:
———————————— e x a m p l e _ 0 example\_0 example_0—————————————
了解过递归和归纳方法的同学肯定都很熟悉 F i b o n a c c i Fibonacci Fibonacci 序列问题,即 f 1 = 1 , f 2 = 1 , f 3 = 2 , f 4 = 3 , … f_1=1,f_2=1,f_3=2,f_4=3,\dots f1=1,f2=1,f3=2,f4=3,,序列中每一个数是其前两个数之和,因此我们可以得到该序列的递归定义:
f ( n ) = { 1 i f    n = 1    o r    n = 2 f ( n − 1 ) + f ( n − 2 ) i f    n ⩾ 3 f(n)=\begin{cases}1\hspace{1cm}if\space\space n=1\space\space or\space\space n=2\\\\ f(n-1)+f(n-2)\hspace{1cm}if\space\space n\geqslant3\end{cases} f(n)=1if  n=1  or  n=2f(n1)+f(n2)if  n3,可见求解 F i b o n a c c i Fibonacci Fibonacci 序列问题的递归形式

  • 1.   p r o c e d u r e    f ( n ) \hspace{1cm}1.\space procedure\space\space f(n) 1. procedure  f(n)
  • 2.   i f    ( n = 1 )    o r    ( n = 2 )    t h e n    r e t u r n    1 \hspace{1cm}2.\space if\space\space(n=1)\space\space or\space\space(n=2)\space\space then\space\space return\space\space1 2. if  (n=1)  or  (n=2)  then  return  1
  • 3.   e l s e    r e t u r n    f ( n − 1 ) + f ( n − 2 ) \hspace{1cm}3.\space else\space\space return\space\space f(n-1)+f(n-2) 3. else  return  f(n1)+f(n2)

这种递归形式是许多递归算法的核心思路,它的优点在于能够将一些复杂的算法用递归的形式简洁地表示出来,但是它的缺点也很明显,有时候递归思路能够给出一个极其有效的算法,有时候却会因重复调用而产生巨大的浪费,例如我们现在将 F i b o n a c c i Fibonacci Fibonacci 序列的递推式做适当的展开:
f ( n ) = f ( n − 1 ) + f ( n − 2 ) f(n)=f(n-1)+f(n-2) f(n)=f(n1)+f(n2)
= 2 f ( n − 2 ) + f ( n − 3 ) \hspace{0.78cm}=2f(n-2)+f(n-3) =2f(n2)+f(n3)
= 3 f ( n − 3 ) + 2 f ( n − 4 ) \hspace{0.78cm}=3f(n-3)+2f(n-4) =3f(n3)+2f(n4)
= 5 f ( n − 4 ) + 3 f ( n − 5 ) \hspace{0.78cm}=5f(n-4)+3f(n-5) =5f(n4)+3f(n5)
这里假设计算 f ( 1 ) f(1) f(1) f ( 2 ) f(2) f(2) 需要一个单位的时间,那么我们可以得到上述过程的时间复杂度为
T ( n ) = { 1 i f    n = 1    o r    n = 2 T ( n − 1 ) + T ( n − 2 ) i f    n ⩾ 3 T(n)=\begin{cases}1\hspace{1cm}if\space\space n=1\space\space or\space\space n=2\\\\ T(n-1)+T(n-2)\hspace{1cm}if\space\space n\geqslant3\end{cases} T(n)=1if  n=1  or  n=2T(n1)+T(n2)if  n3,进而可知 T ( n ) = f ( n ) T(n)=f(n) T(n)=f(n)。那么当 n n n 很大时,根据相关结论,有 f ( n ) ≈ Θ ( ϕ n ) f(n)\approx\Theta(\phi^n) f(n)Θ(ϕn),其中 ϕ = 1 + 5 2 ≈ 1.61803 \phi=\frac{1+\sqrt{5}}{2}\approx1.61803 ϕ=21+5 1.61803,即计算 f ( n ) f(n) f(n) 所耗费的时间是指数增长的。但是,我们可以想一想,之所以产生这么大的浪费,根源是我们是自顶向下进行的递归,但是如果我们换一种思路,从 f 1 , f 2 f_1,f_2 f1,f2 开始自底向上地计算直至 f n f_n fn,那么每一个值只需要计算一次,即只需要 Θ ( n ) \Theta(n) Θ(n) 时间和 Θ ( 1 ) \Theta(1) Θ(1) 空间,时间复杂度大大降低。
—————————————————————————————————
到这里相信大家对自底向上的优势已经有了初步的认识,下面我们结合例子来看一下动态规划的基本思想:

  • 动态规划的核心就是分治和消除冗余
  • 所谓“分治”,即将问题实例分解为更小的、相似的字问题。在上述例子中表现为 f ( n ) = f ( n − 1 ) + f ( n − 2 ) f(n)=f(n-1)+f(n-2) f(n)=f(n1)+f(n2)
  • 所谓“消除冗余”,即将子问题的解存储起来,从而避免重复运算相同的字问题而造成浪费。在上述例子中,冗余表现为计算 f ( n ) f(n) f(n) 需要计算 f ( n − 1 ) f(n-1) f(n1) f ( n − 2 ) f(n-2) f(n2) 的,但是为了得到 f ( n − 1 ) f(n-1) f(n1) 的值,我们又计算了 f ( n − 2 ) f(n-2) f(n2) f ( n − 3 ) f(n-3) f(n3) 的值,这里我们就计算了两次 f ( n − 2 ) f(n-2) f(n2) 的值,这样不断递归下去,当 n n n 的值非常大时,因为重复运算而造成的浪费是很可怕的。

此时,我们可以初步得到动态规划算法的基本步骤:

  • 找出最优解的性质,并刻划其结构特征
  • 递归地定义最优值
  • 以自底向上的方式计算出最优值
  • 根据计算最优值时得到的信息,构造最优解

2. 简单运用
动态规划在许多算法中都扮演着不可或缺的角色,这里我们列举一些典型算法,一起体会一下动态规划的奥妙之处。
最长公共子序列问题
———————————— e x a m p l e _ 1 example\_1 example_1——————————————
在字母表 ∑ \sum 上,分别给出两个长度为 n n n m m m 的字符串 A , B A,B A,B,确定在 A A A B B B 中最长公共子序列的长度。所谓子序列,即假设字符串 A = a 1 a 2 ⋯ a n A=a_1a_2\cdots a_n A=a1a2an,若有字符串 A ′ = a i 1 a i 2 ⋯ a i k A'=a_{i_1}a_{i_2}\cdots a_{i_k} A=ai1ai2aik,其中每个 i j i_j ij 都在 1 1 1 n n n 之间,且 1 ⩽ i 1 < i 2 < ⋯ < i k ⩽ n 1\leqslant i_1<i_2<\cdots<i_k\leqslant n 1i1<i2<<ikn,则称 A ′ A' A A A A 的一个子序列。下面我们根据一个例子来简单理解一下最长公共子序列问题:
现在有 ∑ = { x , y , z } , A = z x y x y z , B = x y y z x \sum=\{x,y,z\},A=zxyxyz,B=xyyzx ={x,y,z},A=zxyxyz,B=xyyzx,根据子序列的定义, x y y xyy xyy A A A 的子序列,同时也是 B B B 的子序列,那么我们可以将 x y y xyy xyy 称为 A A A B B B 的公共子序列,易知其长度为 3 3 3。然而,它并不是 A A A B B B 的最长公共子序列,因为字符串 x y y z xyyz xyyz 也是 A A A B B B 的子序列,并且它的长度为 4 4 4。同时,由于 A A A B B B 没有比 4 4 4 更长的公共子序列,因此这道题的解为 4 4 4
这一问题的传统算法自然是直接进行暴力搜索:列举 A A A 中所有的 2 n 2^n 2n 个子序列,对于每一个子序列再在 Θ ( m ) \Theta(m) Θ(m) 的时间内确定它是否是 B B B 的子序列,显然此算法的时间复杂度为 Θ ( m 2 n ) \Theta(m2^n) Θ(m2n),是指数复杂性的,一般来说很难接受。这里我们来回顾一下动态规划:
首先,我们需要找到一个求解最长公共子序列长度的递推公式,令 A = a 1 a 2 ⋯ a n A=a_1a_2\cdots a_n A=a1a2an B = b 1 b 2 ⋯ b m B=b_1b_2\cdots b_m B=b1b2bm,此外,令 L [ i , j ] L[i,j] L[i,j] 表示 a 1 a 2 ⋯ a i a_1a_2\cdots a_i a1a2ai b 1 b 2 ⋯ b j b_1b_2\cdots b_j b1b2bj 的最长公共子序列的长度,注意 i , j i,j i,j 都有可能是 0 0 0,即 a 1 a 2 ⋯ a i a_1a_2\cdots a_i a1a2ai b 1 b 2 ⋯ b j b_1b_2\cdots b_j b1b2bj 中的一个或同时为空字符串,那么我们可以得到求解 A , B A,B A,B 的最长公共子序列长度的递推式:
L [ i , j ] = { 0 i f    i = 0    o r    j = 0 L [ i − 1 , j − 1 ] + 1 i f    i > 0 , j > 0    a n d    a i = b j max ⁡ { L [ i , j − 1 ] , L [ i − 1 , j ] } i f    i > 0 , j > 0    a n d    a i ≠ b j L[i,j]=\begin{cases}0\hspace{1cm}if\space\space i=0\space\space or\space\space j=0\\\\L[i-1,j-1]+1\hspace{1cm}if\space\space i>0,j>0\space\space and\space\space a_i=b_j\\\\\max\{L[i,j-1],L[i-1,j]\}\hspace{1cm}if\space\space i>0,j>0\space\space and\space\space a_i\neq b_j\end{cases} L[i,j]=0if  i=0  or  j=0L[i1,j1]+1if  i>0,j>0  and  ai=bjmax{L[i,j1],L[i1,j]}if  i>0,j>0  and  ai=bj
此时,根据动态规划自底向上的思路,对于每一对 i i i j j j 的值, 0 ⩽ i ⩽ n , 0 ⩽ j ⩽ m 0\leqslant i\leqslant n,0\leqslant j\leqslant m 0in,0jm,我们用一个 ( n + 1 ) × ( m + 1 ) (n+1)\times(m+1) (n+1)×(m+1) 的表来计算并存储 L [ i , j ] L[i,j] L[i,j] 的值,进而得到算法 L C S LCS LCS

  • 输入:字母表 ∑ \sum 上的两个字符串 A , B A,B A,B,长度分别为 n n n m m m
  • 输出: A A A B B B 最长公共子序列的长度
  • 1.   f o r    i ← 0    t o    n \hspace{1cm}1.\space for\space\space i\leftarrow0\space\space to\space\space n 1. for  i0  to  n
  • 2.   L [ i , 0 ] ← 0 \hspace{1cm}2.\space\hspace{1cm}L[i,0]\leftarrow0 2. L[i,0]0
  • 3.   e n d   f o r \hspace{1cm}3.\space end\space for 3. end for
  • 4.   f o r    j ← 0    t o    m \hspace{1cm}4.\space for\space\space j\leftarrow0\space\space to\space\space m 4. for  j0  to  m
  • 5.   L [ 0 , j ] ← 0 \hspace{1cm}5.\space\hspace{1cm}L[0,j]\leftarrow0 5. L[0,j]0
  • 6.   e n d   f o r \hspace{1cm}6.\space end\space for 6. end for
  • 7.   f o r    i ← 1    t o    n \hspace{1cm}7.\space for\space\space i\leftarrow1\space\space to\space\space n 7. for  i1  to  n
  • 8.   f o r    j ← 1    t o    m \hspace{1cm}8.\space\hspace{1cm}for\space\space j\leftarrow1\space\space to\space\space m 8. for  j1  to  m
  • 9.   i f    a i = b j    t h e n    L [ i , j ] ← L [ i − 1 , j − 1 ] + 1 \hspace{1cm}9.\space\hspace{2cm}if\space\space a_i=b_j\space\space then\space\space L[i,j]\leftarrow L[i-1,j-1]+1 9. if  ai=bj  then  L[i,j]L[i1,j1]+1
  • 10.   e l s e    L [ i , j ] ← max ⁡ { L [ i , j − 1 ] , L [ i − 1 , j ] } \hspace{0.85cm}10.\space\hspace{2cm}else\space\space L[i,j]\leftarrow\max\{L[i,j-1],L[i-1,j]\} 10. else  L[i,j]max{L[i,j1],L[i1,j]}
  • 11.   e n d   i f \hspace{0.85cm}11.\space\hspace{2cm}end\space if 11. end if
  • 12.   e n d   f o r \hspace{0.85cm}12.\space\hspace{1cm}end\space for 12. end for
  • 13.   e n d   f o r \hspace{0.85cm}13.\space end\space for 13. end for
  • 14.   r e t u r n    L [ n , m ] \hspace{0.85cm}14.\space return\space\space L[n,m] 14. return  L[n,m]

显然,由于计算表的每项输入需要 Θ ( 1 ) \Theta(1) Θ(1) 时间,那么算法的时间复杂度正好是表的大小 Θ ( n m ) \Theta(nm) Θ(nm)。此外,易知最长公共子序列问题的最优解只需要耗费 Θ ( min ⁡ { m , n } ) \Theta(\min\{m,n\}) Θ(min{m,n}) 的空间。
—————————————————————————————————

矩阵链相乘
———————————— e x a m p l e _ 2 example\_2 example_2——————————————
假设我们要用标准的矩阵乘法来计算 M 1 , M 2 , M 3 M_1,M_2,M_3 M1,M2,M3 三个矩阵的乘积,这三个矩阵的规格分别为 2 × 10 , 10 × , 2 × 10 2\times10,10\times,2\times10 2×10,10×,2×10。现在如果我们先把 M 1 M_1 M1 M 2 M_2 M2 相乘,然后把结果与 M 3 M_3 M3 相乘,显然需要执行 2 × 10 × 2 + 2 × 2 × 10 = 80 2\times10\times2+2\times2\times10=80 2×10×2+2×2×10=80 次乘法操作;如果使用结合率,用 M 1 M_1 M1 去乘 M 2 , M 3 M_2,M_3 M2,M3 的乘积,那么就需要执行 10 × 2 × 10 + 2 × 10 × 10 = 400 10\times2\times10+2\times10\times10=400 10×2×10+2×10×10=400 次乘法操作,易知执行乘法 M 1 ( M 2 M 3 ) M_1(M_2M_3) M1(M2M3) 耗费的时间是执行乘法 ( M 1 M 2 ) M 3 (M_1M_2)M_3 (M1M2)M3 5 5 5 倍。
因此我们知道,一般来说, n n n 个矩阵 M 1 M 2 ⋯ M n M_1M_2\cdots M_n M1M2Mn 链乘法的耗费,取决于 n − 1 n-1 n1 个乘法执行的次序,因此,矩阵链相乘问题就是找到 M 1 M 2 ⋯ M n M_1M_2\cdots M_n M1M2Mn 按照何种结合方式相乘,所需要的乘法次数最小。同样的,最传统的算法就是暴力搜索:

  1. 找出所有可能的相乘结合方式;
  2. 计算每种相乘结合方式所需要的乘法次数;
  3. min ⁡ \min min

举个例子,现在有 4 4 4 个矩阵 M 1 , M 2 , M 3 , M 4 M_1,M_2,M_3,M_4 M1,M2,M3,M4,暴力搜索算法将试算所有可能的相乘结合方式,在该例子中,一共是 5 5 5 中顺序:
( M 1 ( M 2 ( M 3 M 4 ) ) ) (M_1(M_2(M_3M_4))) (M1(M2(M3M4)))
( M 1 ( ( M 2 M 3 ) M 4 ) ) (M_1((M_2M_3)M_4)) (M1((M2M3)M4))
( ( M 1 M 2 ) ( M 3 M 4 ) ) ((M_1M_2)(M_3M_4)) ((M1M2)(M3M4))
( ( ( M 1 M 2 ) M 3 ) M 4 ) (((M_1M_2)M_3)M_4) (((M1M2)M3)M4)
( ( M 1 ( M 2 M 3 ) ) M 4 ) ((M_1(M_2M_3))M_4) ((M1(M2M3))M4)
一般来说,顺序数等于乘这 n n n 个矩阵时用每一种可能的途径放置小括号的方法数。设 f ( n ) f(n) f(n) 是求 n n n 个矩阵乘积的所有放置小括号的方法数,假定要进行乘法 ( M 1 M 2 ⋯ M k ) × ( M k + 1 M k + 2 ⋯ M n ) (M_1M_2\cdots M_k)\times(M_{k+1}M_{k+2}\cdots M_n) (M1M2Mk)×(Mk+1Mk+2Mn),那么对于前 k k k 个矩阵有 f ( k ) f(k) f(k) 种方法放置小括号,对于 f ( k ) f(k) f(k) 中的每一种方法,余下的 n − k n-k nk 个矩阵另有 f ( n − k ) f(n-k) f(nk) 种放置小括号的方法,因此总计 f ( k ) f ( n − k ) f(k)f(n-k) f(k)f(nk) 种方法。由于 k k k 是介于 1 1 1 n − 1 n-1 n1 中的任意值,我们可以得到 n n n 个矩阵放置小括号的所有方法数: f ( n ) = ∑ k = 1 n − 1 f ( k ) f ( n − k ) f(n)=\sum_{k=1}^{n-1}f(k)f(n-k) f(n)=k=1n1f(k)f(nk)
显然,两个矩阵相乘只有一种方法,三个矩阵相乘则有两种方法。因此,易知 f ( 2 ) = 1 , f ( 3 ) = 2 f(2)=1,f(3)=2 f(2)=1,f(3)=2,为了使递推式有意义,我们人为地规定 f ( 1 ) = 1 f(1)=1 f(1)=1,进而得到
f ( n ) = 1 n (   n − 1 2 n − 2 ) = ( 2 n − 2 ) ! n ( ( n − 1 ) ! ) 2 f(n)=\frac{1}{n}(_{\space n-1}^{2n-2})=\frac{(2n-2)!}{n((n-1)!)^2} f(n)=n1( n12n2)=n((n1)!)2(2n2)!,已知 n ! ≈ 2 π n ( n e ) n , e = 2.71828 ⋯ n!\approx\sqrt{2\pi n}(\frac{n}{e})^n,e=2.71828\cdots n!2πn (en)n,e=2.71828
⇒ f ( n ) = ( 2 n − 2 ) ! n ( ( n − 1 ) ! ) 2 ≈ 4 n 4 π n 1.5 \Rightarrow f(n)=\frac{(2n-2)!}{n((n-1)!)^2}\approx\frac{4^n}{4\sqrt{\pi}n^{1.5}} f(n)=n((n1)!)2(2n2)!4π n1.54n
⇒ f ( n ) = Ω ( 4 n n 1.5 ) \Rightarrow f(n)=\Omega(\frac{4^n}{n^{1.5}}) f(n)=Ω(n1.54n)
由于对于每个小括号化表达式,找到数量乘法次数的时间耗费是 Θ ( n ) \Theta(n) Θ(n),这样用暴力搜索算法可以求得找到 n n n 个矩阵相乘的最优方法所需的运行时间是 Ω ( 4 n n ) \Omega(\frac{4^n}{\sqrt{n}}) Ω(n 4n),这个时间复杂度是无法接受的。
但是,如果我们能够导出一个求解乘法的最少次数的递推关系式,就可以利用动态规划设计出一个更加高效的算法。由于对于每个 i , 1 ⩽ i < n i,1\leqslant i<n i,1i<n,矩阵 M i M_i Mi 的列数一定等于 M i + 1 M_{i+1} Mi+1 的行数(否则不符合矩阵乘法的前提),因此指定每个矩阵的行数末尾矩阵 M n M_n Mn 的列数就足够了。
假设现在有 n + 1 n+1 n+1 维数 r 1 , r 2 , … , r n + 1 r_1,r_2,\dots,r_{n+1} r1,r2,,rn+1,这里 r i , r i + 1 r_i,r_{i+1} ri,ri+1 分别是矩阵 M i M_i Mi 的行数和列数, 1 ⩽ i ⩽ n 1\leqslant i\leqslant n 1in。这里,我们使用 M i , j M_{i,j} Mi,j 来记录 M i M i + 1 ⋯ M j M_iM_{i+1}\cdots M_j MiMi+1Mj 的乘积。此外,假设矩阵链 M i , j M_{i,j} Mi,j 的乘法的耗费用数量乘法的次数来测度,记为 C [ i , j ] C[i,j] C[i,j]。对于给定的一对索引 i , j , 1 ⩽ i < j ⩽ n , M i , j i,j,1\leqslant i<j\leqslant n,M_{i,j} i,j,1i<jn,Mi,j 的计算方法为:
k k k i + 1 i+1 i+1 j j j 之间的一个索引,计算两个矩阵 M i , k − 1 = M i M i + 1 ⋯ M k − 1 M_{i,k-1}=M_iM_{i+1}\cdots M_{k-1} Mi,k1=MiMi+1Mk1 M k , j = M k M k + 1 ⋯ M j M_{k,j}=M_kM_{k+1}\cdots M_j Mk,j=MkMk+1Mj,那么 M i . j = M i , k − 1 M k , j M_{i.j}=M_{i,k-1}M_{k,j} Mi.j=Mi,k1Mk,j。显然,用这种方式计算 M i , j M_{i,j} Mi,j 的总耗费,是计算 M i , k − 1 M_{i,k-1} Mi,k1 的耗费加上计算 M k , j M_{k,j} Mk,j 的耗费,在加上 M i , k − 1 × M k , j M_{i,k-1}\times M_{k,j} Mi,k1×Mk,j 的耗费( r i r k r j + 1 r_ir_kr_{j+1} rirkrj+1),即有
C [ i , j ] = min ⁡ i < k ⩽ j { C [ i , k − 1 ] + C [ k , j ] + r i r k r j + 1 } C[i,j]=\min_{i<k\leqslant j}\{C[i,k-1]+C[k,j]+r_ir_kr_{j+1}\} C[i,j]=mini<kj{C[i,k1]+C[k,j]+rirkrj+1}
因此,对于找出执行矩阵乘法 M 1 M 2 ⋯ M n M_1M_2\cdots M_n M1M2Mn 所需要数量乘法的最小次数,本质上就是求解如下递推式:
C [ 1 , n ] = min ⁡ 1 < k ⩽ n { C [ 1. k − 1 ] + C [ k , n ] + r 1 r k r n + 1 } C[1,n]=\min_{1<k\leqslant n}\{C[1.k-1]+C[k,n]+r_1r_kr_{n+1}\} C[1,n]=min1<kn{C[1.k1]+C[k,n]+r1rkrn+1}
但是,如果我们直接自顶向下求解递推式,同样会导致巨大数量的重复递归调用,进而浪费资源,因此,我们需要采用动态规划自底向上的思路来消除冗余。最终得到算法 m a t c h a i n matchain matchain

  • 输入: r [ 1 ⋯ n + 1 ] r[1\cdots n+1] r[1n+1],表示 n n n 个矩阵规模的 n + 1 n+1 n+1 个整数
  • 输出: n n n 个矩阵连乘的最小乘法次数
  • 1.   f o r    i ← 1    t o    n    { \hspace{1cm}1.\space for\space\space i\leftarrow1\space\space to\space\space n\space\space\{ 1. for  i1  to  n  {填充对角线 d 0 } d_0\} d0}
  • 2.   C [ i , j ] ← 0 \hspace{1cm}2.\space\hspace{1cm}C[i,j]\leftarrow0 2. C[i,j]0
  • 3.   e n d   f o r \hspace{1cm}3.\space end\space for 3. end for
  • 4.   f o r    d ← 1    t o    n − 1    { \hspace{1cm}4.\space for\space\space d\leftarrow1\space\space to\space\space n-1\space\space\{ 4. for  d1  to  n1  {填充对角线 d 1 d_1 d1 d n − 1 } d_{n-1}\} dn1}
  • 5.   f o r    i ← 1    t o    n − d    { \hspace{1cm}5.\space\hspace{1cm}for\space\space i\leftarrow1\space\space to\space\space n-d\space\space\{ 5. for  i1  to  nd  {填充对角线 d i d_i di 的每个项目 } \} }
  • 6.   j ← i + d    { \hspace{1cm}6.\space\hspace{2cm}j\leftarrow i+d\space\space\{ 6. ji+d  {该对角线上 j , i j,i j,i 满足的关系 } \} }
  • 7.   C [ i . j ] ← ∞ \hspace{1cm}7.\space\hspace{2cm}C[i.j]\leftarrow\infty 7. C[i.j]
  • 8.   f o r    k ← i + 1    t o    j \hspace{1cm}8.\space\hspace{2cm}for\space\space k\leftarrow i+1\space\space to\space\space j 8. for  ki+1  to  j
  • 9.   C [ i , j ] ← min ⁡ { C [ i , j ] , C [ i , k − 1 ] + C [ k , j ] + r i × r k × r j + 1 } \hspace{1cm}9.\space\hspace{3cm}C[i,j]\leftarrow\min\{C[i,j],C[i,k-1]+C[k,j]+r_i\times r_k\times r_{j+1}\} 9. C[i,j]min{C[i,j],C[i,k1]+C[k,j]+ri×rk×rj+1}
  • 10.   e n d   f o r \hspace{0.85cm}10.\space\hspace{2cm}end\space for 10. end for
  • 11.   e n d   f o r \hspace{0.85cm}11.\space\hspace{1cm}end\space for 11. end for
  • 12.   e n d   f o r \hspace{0.85cm}12.\space end\space for 12. end for
  • 13.   r e t u r n    C [ 1 , n ] \hspace{0.85cm}13.\space return\space\space C[1,n] 13. return  C[1,n]

⇒ T ( n ) = ∑ d = 1 n − 1 ∑ i = 1 n − d ∑ k = i + 1 j c \Rightarrow T(n)=\sum_{d=1}^{n-1}\sum_{i=1}^{n-d}\sum_{k=i+1}^{j}c T(n)=d=1n1i=1ndk=i+1jc
= ∑ d = 1 n − 1 ∑ i = 1 n − d ∑ k = i + 1 i + d c \hspace{1.27cm}=\sum_{d=1}^{n-1}\sum_{i=1}^{n-d}\sum_{k=i+1}^{i+d}c =d=1n1i=1ndk=i+1i+dc
= ∑ d = 1 n − 1 ∑ i = 1 n − d ∑ k = 1 d c \hspace{1.27cm}=\sum_{d=1}^{n-1}\sum_{i=1}^{n-d}\sum_{k=1}^dc =d=1n1i=1ndk=1dc
= ∑ d = 1 n − 1 ∑ i = 1 n − d c d \hspace{1.27cm}=\sum_{d=1}^{n-1}\sum_{i=1}^{n-d}cd =d=1n1i=1ndcd
= c ∑ d = 1 n − 1 ∑ i = 1 n − d d \hspace{1.27cm}=c\sum_{d=1}^{n-1}\sum_{i=1}^{n-d}d =cd=1n1i=1ndd
= c ( ∑ i = 1 n − 1 1 + ∑ i = 1 n − 2 2 + ∑ i = 1 n − 3 3 + ⋯ + ∑ i = 1 n − ( n − 1 ) ( n − 1 ) ) \hspace{1.27cm}=c\big(\sum_{i=1}^{n-1}1+\sum_{i=1}^{n-2}2+\sum_{i=1}^{n-3}3+\cdots+\sum_{i=1}^{n-(n-1)}(n-1)\big) =c(i=1n11+i=1n22+i=1n33++i=1n(n1)(n1))
= c ( ( n − 1 ) ⋅ 1 + ( n − 2 ) ⋅ 2 + ( n − 3 ) ⋅ 3 + ⋯ + ( n − ( n − 1 ) ) ⋅ ( n − 1 ) ) \hspace{1.27cm}=c\big((n-1)\cdot1+(n-2)\cdot2+(n-3)\cdot3+\cdots+(n-(n-1))\cdot(n-1)\big) =c((n1)1+(n2)2+(n3)3++(n(n1))(n1))
= c ( n ⋅ 1 + n ⋅ 2 + n ⋅ 3 + ⋯ + n ⋅ ( n − 1 ) − 1 ⋅ 1 − 2 ⋅ 2 − ( n − 1 ) ⋅ ( n − 1 ) ) \hspace{1.27cm}=c\big(n\cdot1+n\cdot2+n\cdot3+\cdots+n\cdot(n-1)-1\cdot1-2\cdot2-(n-1)\cdot(n-1)\big) =c(n1+n2+n3++n(n1)1122(n1)(n1))
= c ( n ( 1 + 2 + 3 + ⋯ + ( n − 1 ) ) − ∑ k = 1 n − 1 k 2 ) \hspace{1.27cm}=c\big(n(1+2+3+\cdots+(n-1))-\sum_{k=1}^{n-1}k^2\big) =c(n(1+2+3++(n1))k=1n1k2)
= c ( n ⋅ n ( n − 1 ) 2 − 1 6 ( n − 1 ) n ( 2 n − 1 ) ) \hspace{1.27cm}=c\big(n\cdot\frac{n(n-1)}{2}-\frac{1}{6}(n-1)n(2n-1)\big) =c(n2n(n1)61(n1)n(2n1))
= 1 6 ( c n 3 − c n ) = Θ ( n 3 ) \hspace{1.27cm}=\frac{1}{6}(cn^3-cn)=\Theta(n^3) =61(cn3cn)=Θ(n3)
这个算法的思路可能没那么好理解,但是可以来看下面的一个实例分析:
已知矩阵链 ( M 1 ) 5 × 10 ⋅ ( M 2 ) 10 × 4 ⋅ ( M 3 ) 4 × 6 ⋅ ( M 4 ) 6 × 10 ⋅ ( M 5 ) 10 × 2 (M_1)_{5\times10}\cdot(M_2)_{10\times4}\cdot(M_3)_{4\times6}\cdot(M_4)_{6\times10}\cdot(M_5)_{10\times2} (M1)5×10(M2)10×4(M3)4×6(M4)6×10(M5)10×2
所以有 r 1 = 5 , r 2 = 10 , r 3 = 4 , r 4 = 6 , r 5 = 10 , r 6 = 2 r_1=5,r_2=10,r_3=4,r_4=6,r_5=10,r_6=2 r1=5,r2=10,r3=4,r4=6,r5=10,r6=2

d = 0 d=0 d=0 d = 1 d=1 d=1 d = 2 d=2 d=2 d = 3 d=3 d=3 d = 4 d=4 d=4
C [ 1 , 1 ] = 0    ( M 1 ) C[1,1]=0\space\space(M_1) C[1,1]=0  (M1) C [ 1 , 2 ] = 200    ( M 1 M 2 ) C[1,2]=200\space\space(M_1M_2) C[1,2]=200  (M1M2) C [ 1 , 3 ] = 320 C[1,3]=320 C[1,3]=320 C [ 1 , 4 ] = 620 C[1,4]=620 C[1,4]=620 C [ 1 , 5 ] = 348 C[1,5]=348 C[1,5]=348
C [ 2 , 2 ] = 0    ( M 2 ) C[2,2]=0\space\space(M_2) C[2,2]=0  (M2) C [ 2 , 3 ] = 240    ( M 2 M 3 ) C[2,3]=240\space\space(M_2M_3) C[2,3]=240  (M2M3) C [ 2 , 4 ] = 640    ( M 2 ) ( M 2 M 3 ) C[2,4]=640\space\space(M_2)(M_2M_3) C[2,4]=640  (M2)(M2M3) C [ 2 , 5 ] = 248 C[2,5]=248 C[2,5]=248
C [ 3 , 3 ] = 0    ( M 3 ) C[3,3]=0\space\space(M_3) C[3,3]=0  (M3) C [ 3 , 4 ] = 240    ( M 3 M 4 ) C[3,4]=240\space\space(M_3M_4) C[3,4]=240  (M3M4) C [ 3 , 5 ] = 168 C[3,5]=168 C[3,5]=168
C [ 4 , 4 ] = 0    ( M 4 ) C[4,4]=0\space\space(M_4) C[4,4]=0  (M4) C [ 4 , 5 ] = 120    ( M 4 M 5 ) C[4,5]=120\space\space(M_4M_5) C[4,5]=120  (M4M5)
C [ 5 , 5 ] = 0    ( M 5 ) C[5,5]=0\space\space(M_5) C[5,5]=0  (M5)

d = 0 d=0 d=0 C [ 1 , 1 ] − C [ 2 , 2 ] − C [ 3 , 3 ] − C [ 4 , 4 ] − C [ 5 , 5 ] C[1,1]-C[2,2]-C[3,3]-C[4,4]-C[5,5] C[1,1]C[2,2]C[3,3]C[4,4]C[5,5]
d = 1 d=1 d=1 C [ 1 , 2 ] − C [ 2 , 3 ] − C [ 3 , 4 ] − C [ 4 , 5 ] C[1,2]-C[2,3]-C[3,4]-C[4,5] C[1,2]C[2,3]C[3,4]C[4,5]
d = 2 d=2 d=2 C [ 1 , 3 ] − C [ 2 , 4 ] − C [ 3 , 5 ] C[1,3]-C[2,4]-C[3,5] C[1,3]C[2,4]C[3,5]
d = 3 d=3 d=3 C [ 1 , 4 ] − C [ 2 , 5 ] C[1,4]-C[2,5] C[1,4]C[2,5]
d = 4 d=4 d=4 C [ 1 , 5 ] C[1,5] C[1,5]
这里我们简单看一下 C [ 2 , 4 ] C[2,4] C[2,4] 的计算过程:
C [ 2 , 4 ] = min ⁡ 2 < k ⩽ 4 { C [ 2 , k − 1 ] + C [ k , 4 ] + r 2 r k r 4 + 1 } C[2,4]=\min_{2<k\leqslant4}\{C[2,k-1]+C[k,4]+r_2r_kr_{4+1}\} C[2,4]=min2<k4{C[2,k1]+C[k,4]+r2rkr4+1}
k = 3 → C [ 2 , 4 ] = C [ 2 , 2 ] + C [ 3 , 4 ] + r 2 r 3 r 5 = 0 + 240 + 10 × 4 × 10 = 640 → ( M 2 ) ( M 3 M 4 ) k=3\rightarrow C[2,4]=C[2,2]+C[3,4]+r_2r_3r_5=0+240+10\times4\times10=640\rightarrow(M_2)(M_3M_4) k=3C[2,4]=C[2,2]+C[3,4]+r2r3r5=0+240+10×4×10=640(M2)(M3M4)
k = 4 → C [ 2 , 4 ] = C [ 2 , 3 ] + C [ 4 , 4 ] + r 2 r 4 r 5 = 240 + 0 + 10 × 6 × 10 = 840 → ( M 2 M 3 ) ( M 4 ) k=4\rightarrow C[2,4]=C[2,3]+C[4,4]+r_2r_4r_5=240+0+10\times6\times10=840\rightarrow(M_2M_3)(M_4) k=4C[2,4]=C[2,3]+C[4,4]+r2r4r5=240+0+10×6×10=840(M2M3)(M4)
这个例子最终的答案即是 C [ 1 , 5 ] = 348 C[1,5]=348 C[1,5]=348 次。
—————————————————————————————————

所有点对的最短路径问题
———————————— e x a m p l e _ 3 example\_3 example_3——————————————
G = ( V , E ) G=(V,E) G=(V,E) 是一个有向图,其中的每条边 ( i , j ) (i,j) (i,j) 都有一个非负的长度值 l [ i , j ] l[i,j] l[i,j],此外,如果从顶点 i i i 到顶点 j j j 没有边,则人为地定义 l [ i , j ] = ∞ l[i,j]=\infty l[i,j]=。所谓“所有点对的最短路径问题”,就是要找出从每个顶点到其它所有顶点的距离(从顶点 x x x 到顶点 y y y 的距离是指从 x x x y y y 的最短路径的长度)。
现在,假设 V = { 1 , 2 , … , n } V=\{1,2,\dots,n\} V={1,2,,n} i , j i,j i,j V V V 中两个不同的顶点,定义 d i , j k d_{i,j}^k di,jk 是从 i i i j j j,并且不经过 { k + 1 , k + 2 , … , n } \{k+1,k+2,\dots,n\} {k+1,k+2,,n} 中任何顶点的最短路径的长度。例如, d i , j 0 = l [ i , j ] , d i , j 1 d_{i,j}^0=l[i,j],d_{i,j}^1 di,j0=l[i,j],di,j1 是从 i i i j j j,除了可能经过顶点 1 1 1 以外,不经过任何其它顶点的最短路径, d i , j 2 d_{i,j}^2 di,j2 i i i j j j,除了可能经过顶点 1 , 2 1,2 1,2 或者同时经过它们以外,不经过任何其它顶点的最短路径等。
因此,我们知道, d i , j n d_{i,j}^n di,jn i i i j j j 的最短路径长度,也就是 i i i j j j 的距离,那么就可以得到如下递推式:
d i , j k = { l [ i , j ] i f    k = 0 min ⁡ { d i , j k − 1 , d i , k k − 1 + d k . j k − 1 } i f    1 ⩽ k ⩽ n d_{i,j}^k=\begin{cases}l[i,j]\hspace{1cm}if\space\space k=0\\\\\min\{d_{i,j}^{k-1},d_{i,k}^{k-1}+d_{k.j}^{k-1}\}\hspace{1cm}if\space\space1\leqslant k\leqslant n\end{cases} di,jk=l[i,j]if  k=0min{di,jk1,di,kk1+dk.jk1}if  1kn
根据动态规划算法的核心思路,我们需要自底向上地求解上述递推式。 F l o y d Floyd Floyd 提出,我们可以用 n + 1 n+1 n+1 n × n n\times n n×n 维矩阵 D 0 , D 1 , … , D n D_0,D_1,\dots,D_n D0,D1,,Dn 来计算最短约束路径的长度。
开始时,如果 i ≠ j i\neq j i=j 并且 ( i , j ) (i,j) (i,j) G G G 中的边,则设置 D 0 [ i , i ] = 0 , D 0 [ i , j ] = l [ i , j ] D_0[i,i]=0,D_0[i,j]=l[i,j] D0[i,i]=0,D0[i,j]=l[i,j],否则令 D 0 [ i , j ] = ∞ D_0[i,j]=\infty D0[i,j]=。然后做 n n n 次迭代,使在第 k k k 次迭代后, D k [ i , j ] D_k[i,j] Dk[i,j] 含有从顶点 i i i j j j,且不经过编号大于 k k k 的任何顶点的最短路径的长度。这样就可以在第 k k k 次迭代中,用公式 D k [ i , j ] = min ⁡ { D k − 1 [ i , j ] , D k − 1 [ i , k ] + D k − 1 [ k , j ] } D_k[i,j]=\min\{D_{k-1}[i,j],D_{k-1}[i,k]+D_{k-1}[k,j]\} Dk[i,j]=min{Dk1[i,j],Dk1[i,k]+Dk1[k,j]} 计算 D k [ i , j ] D_k[i,j] Dk[i,j]。下面我们根据一个实例来理解一下上述思路:
已知如下有向图:

根据定义一步一步计算,得到如下矩阵:
D 0 = [ 0 2 9 8 0 6 1 ∞ 6 ] D_0=\left[\begin{matrix}0&2&9\\\\8&0&6\\\\1&\infty&6\end{matrix}\right]\hspace{1cm} D0=08120966 D 1 = [ 0 2 9 8 0 6 1 3 0 ] D_1=\left[\begin{matrix}0&2&9\\\\8&0&6\\\\1&3&0\end{matrix}\right] D1=081203960

D 2 = [ 0 2 8 8 0 6 1 3 0 ] D_2=\left[\begin{matrix}0&2&8\\\\8&0&6\\\\1&3&0\end{matrix}\right]\hspace{1cm} D2=081203860 D 3 = [ 0 2 8 7 0 6 1 3 0 ] D_3=\left[\begin{matrix}0&2&8\\\\7&0&6\\\\1&3&0\end{matrix}\right] D3=071203860
最终答案就存储在矩阵 D 3 D_3 D3 之中。
根据上面的例子,我们发现在第 k k k 次迭代中,第 k k k 行和第 k k k 列都是不变的,因此可以仅用 D D D 矩阵的一个副本来进行计算,算法 f l o y d floyd floyd 如下:

  • 输入: n × n n\times n n×n 维矩阵 l [ 1 ⋯ n ,   1 ⋯ n ] l[1\cdots n,\space1\cdots n] l[1n, 1n],有向图 G = ( { 1 , 2 , … , n } , E ) G=(\{1,2,\dots,n\},E) G=({1,2,,n},E) 中的边 ( i , j ) (i,j) (i,j) 的长度为 l [ i , j ] l[i,j] l[i,j]
  • 输出:矩阵 D D D,使得 D [ i , j ] D[i,j] D[i,j] 等于 i i i j j j 的距离
  • 1.   D ← l    { \hspace{1cm}1.\space D\leftarrow l\space\space\{ 1. Dl  {将输入矩阵 l l l 复制到 D } D\} D}
  • 2.   f o r    k ← 1    t o    n \hspace{1cm}2.\space for\space\space k\leftarrow1\space\space to\space\space n 2. for  k1  to  n
  • 3.   f o r    i ← 1    t o    n \hspace{1cm}3.\space\hspace{1cm}for\space\space i\leftarrow1\space\space to\space\space n 3. for  i1  to  n
  • 4.   f o r    j ← 1    t o    n \hspace{1cm}4.\space\hspace{2cm}for\space\space j\leftarrow1\space\space to\space\space n 4. for  j1  to  n
  • 5.   D [ i , j ] = min ⁡ { D [ i , j ] , D [ i , k ] + D [ k , j ] } \hspace{1cm}5.\space\hspace{3cm}D[i,j]=\min\{D[i,j],D[i,k]+D[k,j]\} 5. D[i,j]=min{D[i,j],D[i,k]+D[k,j]}
  • 6.   e n d   f o r \hspace{1cm}6.\space\hspace{2cm}end\space for 6. end for
  • 7.   e n d   f o r \hspace{1cm}7.\space\hspace{1cm}end\space for 7. end for
  • 8.   e n d   f o r \hspace{1cm}8.\space end\space for 8. end for

显然,算法的时间复杂度为 Θ ( n 3 ) \Theta(n^3) Θ(n3),空间复杂度为 Θ ( n 2 ) \Theta(n^2) Θ(n2)
—————————————————————————————————

0-1 背包问题
———————————— e x a m p l e _ 4 example\_4 example_4——————————————
问题定义:设 U = { u 1 , u 2 , … , u n } U=\{u_1,u_2,\dots,u_n\} U={u1,u2,,un} 是一个准备放入容量为 C C C 的背包中的 n n n 项物品的集合。对于 1 ⩽ j ⩽ n 1\leqslant j\leqslant n 1jn,令 s j s_j sj v j v_j vj 分别为第 j j j 项物品的体积和价值,这里假设上述 C , s j , v j , j C,s_j,v_j,j C,sj,vj,j 都是正整数。所谓 0-1 背包问题就是用 U U U 中的一些物品来装满背包,这些物品的总体积不超过 C C C,同时使它们的总价值最大。即给出有 n n n 项物品的 U U U,我们要找出一个子集合 S ⊆ U S\subseteq U SU,使得 ∑ u i ∈ S v i \sum_{u_i\in S}v_i uiSvi 在约束条件 ∑ u i ∈ S s i ⩽ C \sum_{u_i\in S}s_i\leqslant C uiSsiC 下最大。(之所以称为 0-1 背包问题,是因为一个物品要么装入,要么不装入,这两种状态分别用 1 和 0 表示)
首先,我们需要导出递推公式,设 V [ i , j ] V[i,j] V[i,j] 用来表示从前 i i i 项物品( { u 1 , u 2 , … , u i } \{u_1,u_2,\dots,u_i\} {u1,u2,,ui})中取出来的装入体积为 j j j 的背包的最大价值。这里, i i i 的范围是从 0 0 0 n n n j j j 的范围是从 0 0 0 C C C,因此最终答案即 V [ n , C ] V[n,C] V[n,C]。显然, V [ 0 , j ] V[0,j] V[0,j] 对于所有 j j j 的值是 0 0 0,因为背包中什么也没有; V [ i , 0 ] V[i,0] V[i,0] 对于所有 i i i 的值也是 0 0 0,因为没有东西可以放到容量为 0 0 0 的背包。而当 i > 0 , j > 0 i>0,j>0 i>0,j>0 时,有

  • V [ i − 1 , j ] V[i-1,j] V[i1,j]:仅用最优的方法取自 { u 1 , u 2 , … , u i − 1 } \{u_1,u_2,\dots,u_{i-1}\} {u1,u2,,ui1} 的物品去装入体积为 j j j 的背包所得的价值最大值
  • V [ i − 1 , j − s i ] + v i V[i-1,j-s_i]+v_i V[i1,jsi]+vi:用最优的方法取自 { u 1 , u 2 , … , u i − 1 } \{u_1,u_2,\dots,u_{i-1}\} {u1,u2,,ui1} 的物品去装入体积为 j − s i j-s_i jsi 的背包所得的价值最大值加上物品 u i u_i ui 的价值。这仅用于若 j ⩾ s i j\geqslant s_i jsi 以及它等于把物品 u i u_i ui 加到背包上的情况
  • V [ i , j ] = max ⁡ { V [ i − 1 , j ] ,   V [ i − 1 , j − s i ] + v i } V[i,j]=\max\{V[i-1,j],\space V[i-1,j-s_i]+v_i\} V[i,j]=max{V[i1,j], V[i1,jsi]+vi}

综上所述,得到
V [ i , j ] = { 0 i f    i = 0    o r    j = 0 V [ i − 1 , j ] i f    j < s i max ⁡ { V [ i − 1 , j ] ,   V [ i − 1 , j − s i ] + v i } i f    i > 0    a n d    j ⩾ s i V[i,j]=\begin{cases}0\hspace{1cm}if\space\space i=0\space\space or\space\space j=0\\\\V[i-1,j]\hspace{1cm}if\space\space j<s_i\\\\\max\{V[i-1,j],\space V[i-1,j-s_i]+v_i\}\hspace{1cm}if\space\space i>0\space\space and\space\space j\geqslant s_i\end{cases} V[i,j]=0if  i=0  or  j=0V[i1,j]if  j<simax{V[i1,j], V[i1,jsi]+vi}if  i>0  and  jsi
根据动态规划的思路,我们自底向上进行计算,并且逐行将值存入 ( n + 1 ) × ( C + 1 ) (n+1)\times(C+1) (n+1)×(C+1) 的表,下面是算法 k n a p s a c k knapsack knapsack

  • 输入:物品集合 U = { u 1 , u 2 , … , u n } U=\{u_1,u_2,\dots,u_n\} U={u1,u2,,un},体积分别为 s 1 , s 2 , … , s n s_1,s_2,\dots,s_n s1,s2,,sn,价值分别为 v 1 , v 2 , … , v n v_1,v_2,\dots,v_n v1,v2,,vn,背包容量 C C C
  • 输出: ∑ u i ∈ S v i \sum_{u_i\in S}v_i uiSvi 的最大总价值,且满足 ∑ u i ∈ S s i ⩽ C ,    S ⊆ U \sum_{u_i\in S}s_i\leqslant C,\space\space S\subseteq U uiSsiC,  SU
  • 1.   f o r    i ← 0    t o    n \hspace{1cm}1.\space for\space\space i\leftarrow0\space\space to\space\space n 1. for  i0  to  n
  • 2.   V [ i , 0 ] ← 0 \hspace{1cm}2.\space\hspace{1cm}V[i,0]\leftarrow0 2. V[i,0]0
  • 3.   e n d   f o r \hspace{1cm}3.\space end\space for 3. end for
  • 4.   f o r    j ← 0    t o    C \hspace{1cm}4.\space for\space\space j\leftarrow0\space\space to\space\space C 4. for  j0  to  C
  • 5.   V [ 0 , j ] ← 0 \hspace{1cm}5.\space\hspace{1cm}V[0,j]\leftarrow0 5. V[0,j]0
  • 6.   e n d   f o r \hspace{1cm}6.\space end\space for 6. end for
  • 7.   f o r    i ← 1    t o    n \hspace{1cm}7.\space for\space\space i\leftarrow1\space\space to\space\space n 7. for  i1  to  n
  • 8.   f o r    j ← 1    t o    C \hspace{1cm}8.\space\hspace{1cm}for\space\space j\leftarrow1\space\space to\space\space C 8. for  j1  to  C
  • 9.   V [ i , j ] ← V [ i − 1 , j ] \hspace{1cm}9.\space\hspace{2cm}V[i,j]\leftarrow V[i-1,j] 9. V[i,j]V[i1,j]
  • 10.   i f    s i ⩽ j    t h e n    V [ i , j ] ← max ⁡ { V [ i , j ] ,   V [ i − 1 , j − s i ] + v i } \hspace{0.85cm}10.\space\hspace{2cm}if\space\space s_i\leqslant j\space\space then\space\space V[i,j]\leftarrow\max\{V[i,j],\space V[i-1,j-s_i]+v_i\} 10. if  sij  then  V[i,j]max{V[i,j], V[i1,jsi]+vi}
  • 11.   e n d   f o r \hspace{0.85cm}11.\space\hspace{1cm}end\space for 11. end for
  • 12.   e n d   f o r \hspace{0.85cm}12.\space end\space for 12. end for
  • 13.   r e t u r n    V [ n , C ] \hspace{0.85cm}13.\space return\space\space V[n,C] 13. return  V[n,C]

显然,由于计算表的每一项需要 Θ ( 1 ) \Theta(1) Θ(1) 时间,算法的时间复杂度恰好是表的大小 Θ ( n C ) \Theta(nC) Θ(nC)。0-1 背包问题的具体实例可参考我之前写过的一篇题解:蓝桥杯——算法训练——开心的金明(附0-1背包讲解)
—————————————————————————————————

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值