算法复习_动态规划

动态规划

切水管

水管一共n英尺长,长度为i的水管售价p[i],如何切割价值最高

r(n)表示切割n英尺长水管的最大价值,我们第一次可以切割出i英尺长的水管
r ( n ) = p i + r ( n − i ) , 0 < i ≤ n r(n) = p_i + r(n-i),\quad 0 < i \leq n r(n)=pi+r(ni),0<in

我们不难证明r(n-i)也是切割长度为n-i英尺水管所带来的最大价值

而第一段钢管具体的长度选择一共有n种可能,我们只需要选择最大的,得到递归方程:
r n = m a x ( p i + r n − i ) r_n = max (p_i+r_{n-i}) rn=max(pi+rni)
递归算法:

CUT_ROD(p,n)
if n==0
	return 0
q = -MAXN
for i=1 to n
	q = max(q,p[i]+CUT_ROD(p,n-i))
return q

时间复杂度: T ( n ) = 1 + ∑ j = 0 n − 1 T ( j ) = 2 n T(n) = 1 + \sum_{j=0}^{n-1}T(j) = 2^n T(n)=1+j=0n1T(j)=2n

指数级的时间复杂度说明了该问题递归不可解,通过画出递归树我们发现时间复杂度过高就在于重复求解了很多相同的子问题。

而动态规划最最本质的思想就在于将你第一次遇到的子问题的解记录下来,当再次遇到该问题时直接去查表得到答案,以存储答案的空间来换取你求解该子问题的时间。

改进上述算法得到

MEMOIZED_CUT_ROD(p,n)
creta a new array r[0,...,n]
for i=0 to n
	r[i] = -MAXN //初始化存储空间,此时代表该子问题无解
return MEMOIZED_CUT_ROD_AUX(p,n,r)

MEMOIZED_CUT_ROAD_AUX(p,n,r)
if r[n]>=0
	return r[n] //代表该问题已经被求解过,直接查表得到
if n==0
	q=0
else q=-MAXN
	for i=1 to n
		q = max(q,p[i]+MEMOIZED_CUT_ROD_AUX(p,n-1,r))
r[n] = q //记录该问题的解
return q

这种算法是一种非常典型的top down算法,即给出一个大规模的问题,不断递归缩小规模,求解出子问题再将子问题的解组织成原问题的解。形象点说就是大问题被压在栈底,小问题在栈顶,问题解决了栈肯定是要空着的,但求解的过程是小问题先出栈,最后是大问题。

同样,也可以使用bottom up算法来解决,先从小问题着手,慢慢遍历到大问题

BOTTOM_UP_CUT_ROD(p,n)
create a new array r[0,...,n]
r[0] = 0
for j=1 to n
	q=-MAXN
	for i=1 to j
		q = max(q,p[i]+r[j-i]) //此时r[j-i]一定是被求解出来的
	r[j] = q
return r[n]

这种算法最大的好处就是更加便于理解。

该算法时间复杂度: Θ ( n 2 ) \Theta(n^2) Θ(n2)

问题升级

记录下最优的切割方案

思路:记录下最优切割方案中第一段水管的长度。

为什么可以这么做:去掉第一段,找出子问题中最优第一段的长度。遍历下去,使得水管长度归0.得到切割方案。

实现:

EXTENDED_BOTTOM_UP_CUT_ROD(p,n)
create new array r[0,...,n] & s[0,...,n]
r[0] = 0
for j=1 to n
	q=-MAXN
	for i=1 to j
		if q < p[i] + r[j-i]
			q = p[i] + r[j-1]
			s[j] = i
	r[k] = q
return r and s

PRINT_CUT_ROD_SOLUTION(p,n)
(r,s)=EXTENDED_BOTTOM_UP_CUT_ROD(p,n)
while n>0
	print s[n]
	n -= s[n]

问题再升级

对长度为N的钢管进行切割,并且切割次数不超过K的情况下的最优切割。

增加状态:r[n,k]表示切割长度n的钢管,次数不超过k的最优解
r ( n , k ) = { 0 n = 0 p [ n ] k = 0 r ( n , n − 1 ) k ≥ n m a x { p [ i ] + r ( n − i , k − 1 ) } 0 < k < n r(n,k) = \left\{ \begin{matrix} 0 \quad n=0 \\ p[n] \quad k=0 \\ r(n,n-1) \quad k \geq n \\ max\{ p[i] + r(n-i,k-1)\} \quad 0 < k < n \end{matrix} \right. r(n,k)=0n=0p[n]k=0r(n,n1)knmax{p[i]+r(ni,k1)}0<k<n
问题最终升级

不考虑切割顺序,则总共有多少种切割方法。

设置状态: p(n,m)把长度为n的钢管切割成长度不超过m的小钢管的方法数量。

最终求解: p(n,n)

状态转移方程:
p ( n , m ) = { 0 n = 0 o r m = 0 1 m = 1 p ( n , n ) m > n p ( n , m − 1 ) + p ( n − m , m ) m ≤ n p (n,m) = \left\{ \begin{matrix} 0 \quad n=0 \quad or \quad m=0 \\ 1 \quad m=1 \\ p(n,n) \quad m>n \\ p(n,m-1) + p(n-m,m) \quad m\leq n \end{matrix} \right. p(n,m)=0n=0orm=01m=1p(n,n)m>np(n,m1)+p(nm,m)mn

背包问题

01背包

给定背包最大容量W,n个物品的重量和价值。求背包能带走的最大价值

建立模型
m a x ∑ i = 1 n v i x i { ∑ i = 1 n w i x i ≤ W x i ∈ { 0 , 1 } , 1 ≤ i ≤ n max \sum_{i=1}^n v_ix_i \\ \left\{ \begin{matrix} \sum_{i=1}^{n} w_ix_i \leq W \\ x_i \in \{0,1\},1\leq i \leq n \end{matrix} \right. maxi=1nvixi{i=1nwixiWxi{0,1},1in
定义opt[i,w]表示只选择前i个物品,保证重量不超过w的最大价值
o p t [ i , w ] = { 0 i f i = 0 o p t [ i − 1 , w ] i f w i > w m a x { o p t [ i − 1 , w ] , v i + o p t [ i − 1 , w − w i ] } o t h e r s opt[i,w] = \left\{ \begin{matrix} 0 \quad if \quad i =0 \\ opt[i-1,w] \quad if \quad w_i > w \\ max\{opt[i-1,w],v_i+opt[i-1,w-w_i]\} \quad others \end{matrix} \right. opt[i,w]=0ifi=0opt[i1,w]ifwi>wmax{opt[i1,w],vi+opt[i1,wwi]}others

Input n,W,w1,...,wn, v1,...,vn
for w=0 to W
	opt[0,w] = 0
for i=1 to n
	for w=1 to W
		if wi > W
			opt[i,w] =opt[i-1,w]
		else
			opt[i,w] =max(opt[i-1][w],opt[i-1][w-wi])
return opt[n,w]

要会根据算法列表

时间复杂度 Θ ( n W ) \Theta(nW) Θ(nW)

无限制背包问题

一个物品可以有很多个

同样定义opt[i,w]表示只选择前i个物品,保证重量不超过w的最大价值

如果选择了第i个物品,那么新的重量更新为 w ′ = w − w i w'=w-w_i w=wwi,再前i个物品继续进行选择。
o p t [ i , w ] = { 0 i f i = 0 o p t [ i − 1 , w ] i f w i > w m a x { o p t [ i − 1 ] [ w ] , v 1 + o p t [ i , w − w i ] } w ≥ w i opt[i,w] = \left\{ \begin{matrix} 0 \quad if\quad i= 0\\ opt[i-1,w] \quad if \quad w_i > w \\ max \{opt[i-1][w],v_1+opt[i,w-w_i] \} \quad w \geq w_i \end{matrix} \right. opt[i,w]=0ifi=0opt[i1,w]ifwi>wmax{opt[i1][w],v1+opt[i,wwi]}wwi
注意:更改之处只在于当决定选择第i个物品时,仍然可以再前i个物品中选择

时间复杂度: Θ ( n W ) \Theta(nW) Θ(nW)

如何理解? 结合你画表的做法,原来是用前一行加上该物品价值和前一行比较,现在只是用当前行加上物品价值和前一行比较。循环的次数并不改变。

有限制背包问题

每个物品有一定的数量限制 K i K_i Ki.

如果选择了第i个物品,则重量更新为 w ′ = w − k w i w'=w-kw_i w=wkwi,在前i-1个物品中进行选择。

状态转移方程:
o p t [ i , w ] = { 0 i f i = 0 o p t [ i − 1 , w ] i f w i > w m a x { o p t [ i − 1 , w − k w i ] + k v i } 0 ≤ k ≤ p ( p = m i n { k i , w w i } ) opt[i,w] = \left\{ \begin{matrix} 0 \quad if \quad i=0 \\ opt[i-1,w] \quad if \quad w_i > w \\ max\{ opt[i-1,w-kw_i] + kv_i\} \quad 0 \leq k \leq p \quad (p = min\{k_i,\frac{w}{w_i}\}) \end{matrix} \right. opt[i,w]=0ifi=0opt[i1,w]ifwi>wmax{opt[i1,wkwi]+kvi}0kp(p=min{ki,wiw})
时间复杂度: Θ ( n W K ) \Theta(nWK) Θ(nWK)

循环次数一致,但在每次做选择的时候,需要比较K种情况。

矩阵链相乘

p*q维矩阵和q*r维矩阵相乘的时间复杂度是p*q*r

给定一个矩阵序列, A 1 , A 2 , A 3 , … , A n A_1,A_2,A_3,\dots,A_n A1,A2,A3,,An,以及维度序列 p 0 , p 1 , … , p n p_0,p_1,\dots,p_n p0,p1,,pn,使得矩阵 A i A_i Ai的维度是 p i − 1 ∗ p i p_{i-1} * p_i pi1pi。最小化该矩阵链相乘的时间。

该问题的实质在于找到最好的相乘顺序。如果有n个矩阵,我们有n-1个位置可以将矩阵划分成两个部分,即第一个矩阵后…第n-1个矩阵之后。当我们在第k个矩阵之后划分,那么矩阵变成了两个部分,一个部分有k个,另一个有n-k个。我们只要继续考虑如何划分左右两部分就ok。如果左边有l种划分可能,右边有r种划分可能。那么这个问题需要考虑的数量就是l*r。

一共有多少种划分的可能性?
P ( n ) = { 1 n = 1 ∑ i = 1 n − 1 P ( i ) P ( n − i ) n > 1 P(n) = \left\{ \begin{matrix} 1 \quad n=1 \\ \sum_{i=1}^{n-1} P(i)P(n-i) \quad n>1 \end{matrix} \right. P(n)={1n=1i=1n1P(i)P(ni)n>1
时间复杂度过高,考虑采用dp。

划分如下
A 1 , 2 , … , n = A 1 , 2 , … , k A k + 1 , … , n A_{1,2,\dots,n} = A_{1,2,\dots,k}A_{k+1,\dots,n} A1,2,,n=A1,2,,kAk+1,,n
如何划分?我们并不知道如何划分最优,需要遍历n-1种可能。

定义状态m[i,j]表示矩阵链 A i , i + 1 … , j A_{i,i+1\dots,j} Ai,i+1,j相乘的最少相乘次数。状态转移方程如下:
m [ i , j ] = { 0 i f i ≥ j m i n { m [ i , k ] + m [ k , i ] + p i − 1 p k p j } ( i ≤ k < j ) m[i,j] = \left\{ \begin{matrix} 0 \quad if \quad i \geq j \\ min\{m[i,k]+m[k,i]+p_{i-1}p_kp_{j}\} \quad (i\leq k< j) \end{matrix} \right. m[i,j]={0ifijmin{m[i,k]+m[k,i]+pi1pkpj}(ik<j)
实现

MATRIX_CHAIN_ORDER(p)
n = p.length-1
let m[1...n,1...n] and s[1..n-1,2...n] be new tables
for i=1 to n
	m[i,i]=0
for l=2 to n
	for i = 1 to n-l+1
		j = i+l-1
		m[i,j]=maxn
		for k =i to j-1
			q = m[i,k] + m[k+1,j] + p[i-1]p[k]p[j]
			if q<m[i,j]
				m[i,j]=q
				s[i,j]=k
return m and s

一眼丁真,鉴定为bottom-up型算法。一共三层嵌套循环,每层至多运行n次,时间复杂度 Θ ( n 3 ) \Theta(n^3) Θ(n3)

打印出最佳路径

PRINT_OPTIMAL_PATHES(s,i,j)
if i==j
	print("A",i)
else
	PRINT_OPTIMAL_PATHES(s,i,s[i,j])
	PRINT_OPTIMAL_PATHES(s,s[i,j]+1,j)

总结

动态规划问题的两个要素

  • 最优子结构
  • 重叠子问题
最优子结构

发掘最优子结构的通用模式

  1. 证明最优解的第一个组成部分是做出一个选择,做出这次选择会产生一个或者多个待解决的问题。
  2. 给定最优解的选择之后,利用剪切粘贴的技术证明,子问题的解就是它本身的最优解。

最优子结构并不适用于所有的最优化问题。

ex:

无权最短路径,从u到v的最短路径必然通过一个中间节点,我们可以将路径u->v划分为u->w->v。即p=p1+p2。我们可以断言如果p是最短路径,那么p1也是u到w的最短路径。我们采用剪切-粘贴法证明,我们假设存在w’使得u到w’存在一条更短的路径,那么我们可以剪切掉p1,将p1’粘贴上,构造出了一条比p更短的路径u->w’->v。与p最优的假设矛盾。因此p1是u->w的最优路径。同理p2可证。

无权最长路径

在这里插入图片描述

重叠子问题

类似于分治算法,dp也是将大问题分解成小问题来递归的解决。

然而,在dp中子问题之间并不是独立的,当子子问题被解决时,它的结果被保存在表中避免重复的计算。

最优二叉搜索树

目的:建立拥有最低预期搜索代价的二叉搜索树。每个搜索键值的搜索代价为该值对应二叉搜索树上的结点的深度+1。

给定 k 1 , k 2 , … , k n k_1,k_2,\dots,k_n k1,k2,,kn这个已经排序的序列以及每个键值被搜索的概率 p i p_i pi。搜索代价的期望如下:
E [ s e a r c h c o s t i n T ] = ∑ i = 1 n ( d e p t h T ( k i ) + 1 ) p i = ∑ i = 1 n d e p t h T p i + ∑ i = 1 n p i = ∑ i = 1 n d e p t h T p i + 1 E[search cost in T] \\ = \sum_{i=1}^{n} (depth_T(k_i)+1)p_i \\ = \sum_{i=1}^{n}depth_Tp_i + \sum_{i=1}^{n} p_i \\ =\sum_{i=1}^{n} depth_Tp_i + 1 E[searchcostinT]=i=1n(depthT(ki)+1)pi=i=1ndepthTpi+i=1npi=i=1ndepthTpi+1
证明:如果T是最优二叉搜索树,T包含拥有键值 k i , … , k j k_i,\dots,k_j ki,,kj的子树T’,子树T’也是最优二叉搜索树。

采用”剪切-粘贴法“。如果存在包含键值 k i , … , k j k_i,\dots,k_j ki,,kj的子树T’‘,它的期望搜索代价要小于T’。那么可以将原来T’替换成T’'。此时T其他部分不受影响,但是期望搜索代价下降,与T是最优二叉搜索树矛盾。因此假设不成立。

如何找到最优BST:

  • 检验所有的候选根节点 k r , i ≤ r ≤ l k_r,i\leq r \leq l kr,irl
  • 决定左子树和右子树的最优结构。

与矩阵链相乘问题类似,e[i,j]表示期望搜索代价,root[i,j]表示拥有键值 k i , … , k j k_i,\dots,k_j ki,,kj子树的根节点。w[i,j]表示可能性之和。状态转移方程:
e [ i , j ] = p r + ( e [ i , r − 1 ] + w ( i , r − 1 ) ) + ( e [ r + 1 , j ] + w ( r + 1 , j ) ) w [ i , j ] = w ( i , r − 1 ) + w ( r + 1 , j ) + p r e [ i , j ] = e [ i , r − 1 ] + e [ r + 1 , j ] + w [ i , j ] e[i,j] = p_r + (e[i,r-1]+w(i,r-1)) + (e[r+1,j]+w(r+1,j)) \\ w[i,j] = w(i,r-1)+ w(r+1,j) + p_r \\ e[i,j] = e[i,r-1] + e[r+1,j] + w[i,j] e[i,j]=pr+(e[i,r1]+w(i,r1))+(e[r+1,j]+w(r+1,j))w[i,j]=w(i,r1)+w(r+1,j)+pre[i,j]=e[i,r1]+e[r+1,j]+w[i,j]
为什么要加上可能性之和,因为代价是(depth+1)*possibility.

递推公式如下:
e [ i , j ] = { 0 j = i − 1 m i n { e [ i , r − 1 ] + e [ r + 1 , j ] + w ( i , j ) } i ≤ r ≤ j e[i,j] =\left\{ \begin{matrix} 0 \quad j=i-1 \\ min \{ e[i,r-1] + e[r+1,j] + w(i,j) \} \quad i \leq r \leq j \end{matrix} \right. e[i,j]={0j=i1min{e[i,r1]+e[r+1,j]+w(i,j)}irj

OPTIMAL_BST(p,q,n)
for i<-1 to n+1
	e[i,i-1] <- 0
	w[i,i-1] <- 0
for l <- 1 to n
	do for i <- 1 to n-l+1
		j = i+l-1
		e[i,j]=maxn
		w[i,j]=w[i,j-1]+pj
		for r <- i to j
			t<-e[i,r-1]+e[r+1,j]+w[i,j]
			if t < e[i,j]
				e[i,j] <- t
				root[i,j] <- r
return e and root

最长公共子序列

给定两个序列 x 1 , x 2 , … , x m x_1,x_2,\dots,x_m x1,x2,,xm y 1 , y 2 , … , y n y_1,y_2,\dots,y_n y1,y2,,yn,找到一个长度最长的公共序列。

子序列不一定要连续,但一定要有序。

定理:如果 z 1 , z 2 , … , z k z_1,z_2,\dots,z_k z1,z2,,zk是x,y的最长公共子序列。

  • 如果Xm=Yn,那么Zk=Xm=Yn,并且 Z k − 1 是 X m − 1 和 Y n − 1 Z_{k-1}是X_{m-1}和Y_{n-1} Zk1Xm1Yn1的LCS。
  • 如果Xm!=Yn,那么 Z k 是 X m − 1 和 Y n Z_k是X_{m-1}和Y_n ZkXm1Yn或者 X m 和 Y n X_m和Y_n XmYn的LCS。

定义c[i,j]为Xi和Yj的LCS长度。

根据上述定理,得到状态转移方程:
c [ i , j ] = { 0 i = 0 o r j = 0 c [ i − 1 , j − 1 ] + 1 x i = y j m a x { c [ i − 1 , j ] , c [ i , j − 1 ] } x i ! = y j c[i,j] = \left\{ \begin{matrix} 0 \quad i=0 \quad or \quad j=0 \\ c[i-1,j-1]+1 \quad x_i=y_j \\ max\{c[i-1,j],c[i,j-1]\} \quad x_i != y_j \end{matrix} \right. c[i,j]=0i=0orj=0c[i1,j1]+1xi=yjmax{c[i1,j],c[i,j1]}xi!=yj
计算LCS的长度

LCS-LENGTH(X,Y)
m <- length(X)
n <- length(Y)
for i<-1 to m
	c[i,0]<-0
for j<-0 to n
	c[0,j]<-0
for i<-1 to m
	for j<-1 to n
		if xi = yj
			c[i,j]<-c[i-1,j-1] + 1
			b[i,j]<-leftup
		else if c[i-1,j] >= c[i,j-1]
			c[i,j]<-c[i-1,j]
			b[i,j]<-up
		else 
			c[i,j]<-c[i,j-1]
			b[i,j]<-left
return c and b

时间复杂度 Θ ( m n ) \Theta(mn) Θ(mn)

打印出LCS

PRINT_LCS(b,X,i,j)
if i=0 or j=0
	return 
if b[i,j]=leftup
	PRINT_LCS(b,X,i-1,j-1)
	print xi
else if b[i,j] = up
	PRINT_LCS(b,X,i-1,j)
else PRINT_LCS(b,X,i,j-1)

在这里插入图片描述

序列划分

将n份任务划分成为k类,使得一类的工作时间之和的最大值最小。

定义d[n,k]为将n份任务划分成k类的最优解。问题的解d[n,k]。

定义s[i,j] = s[i] + s[i+1] + … + s[j]

状态转移方程如下:
d [ n , k ] = { 0 n = 0 m i n 0 ≤ i ≤ n { m a x { d [ n − i , k − 1 ] , s [ n − i + 1 , n ] } } n , k > 0 m a x n k = 0 d[n,k] = \left\{ \begin{matrix} 0 \quad n=0 \\ min_{0\leq i \leq n}\{max\{d[n-i,k-1],s[n-i+1,n]\}\} \quad n,k>0 \\ maxn \quad k=0 \end{matrix} \right. d[n,k]=0n=0min0in{max{d[ni,k1],s[ni+1,n]}}n,k>0maxnk=0
首先当n,k>0时,将n份工作中的后i份工作合并,此时需要确定划分之后,所有类中的最大工作时间,因此max{d[n-i,k-1],s[n-i+1,n]},而我们最终的任务是使得这个最大值最小,因此对所有可能的方案取得最小值作为问题的解。

最长递增子序列LIS

动态规划:以LIS[k]表示序列a[1,…,n]中的元素a[k]结尾的LIS长度。
L I S [ K ] = { 0 k = 0 m a x 0 ≤ i < k & a [ k ] > a [ i ] { L I S [ i ] + 1 } k > 1 LIS[K] = \left\{ \begin{matrix} 0 \quad k=0 \\ max_{0\leq i < k \& a[k] > a[i] } \{LIS[i]+1\} \quad k>1 \end{matrix} \right. LIS[K]={0k=0max0i<k&a[k]>a[i]{LIS[i]+1}k>1
时间复杂度 O ( n 2 ) O(n^2) O(n2)

优化算法:动态规划+二分搜索

高情商:ppt有,中文的,不赘述了。

低情商:tnnd看不懂

字符串删除

从n个字符组成的字符串中删去k个能生成多少个不同的字符串

num(i,k)表示前i个字符组成的字符串删去k个字符串的数量。有多少种可能?从屁股开始看,你删去k个,最后一个字符只能是 s i , s i − 1 , … , s i − k s_i,s_{i-1},\dots,s_{i-k} si,si1,,sik这k种情况,那么我们就讨论这k中情况,看图:

在这里插入图片描述

只要把这k种情况,在二维数组中查表得到的值相加,问题解决。

注意:当 s j = s l 并 且 i ≥ j > l ≥ i − k s_j = s_l 并且 i\geq j > l \geq i-k sj=slij>lik时,则会出现重复的情况。

如下图所示:

在这里插入图片描述

状态转移方程:
n u m ( i , k ) = { 0 k > i 1 k = i ∑ t = i i − k { 0 s t ∈ { s t + 1 , . . . s i } N u m ( t − 1 , k − i + t ) o t h e r s num(i,k)= \left\{ \begin{matrix} 0 \quad k > i \\ 1 \quad k = i \\ \sum_{t=i}^{i-k} \left\{ \begin{matrix} 0 \quad s_t \in \{s_{t+1},...s_i\} \\ Num(t-1,k-i+t) \quad others \end{matrix} \right. \end{matrix} \right. num(i,k)=0k>i1k=it=iik{0st{st+1,...si}Num(t1,ki+t)others

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值