动态规划
写在前面的话
本文是本学渣因为考试需要写的一篇总结,只总结了需要考察的考点,所以可能有的内容引申的不多,请见谅。
一、思想
动态规划与分治方法类似,都是通过组合子问题的解来来求解原问题的。
分治方法将问题划分为互不相交的子问题,递归的求解子问题,再将它们的解组合起来,求出原问题的解。
动态规划应用于子问题重叠的情况,即不同的子问题具有公共的子子问题(子问题的求解是递归进行的,将其划分为更小的子子问题)。这种情况下使用分治算法会重复求解那些子子问题而浪费时间。
二、解决步骤
解决动态规划问题一般分为四步:
1、定义一个状态,这是一个最优解的结构特征
2、进行状态递推,得到递推公式
3、进行初始化
4、返回结果
三、0-1背包问题
给定n个重量为w1 ,w2 ,…,wn,价值为v1 ,v2 ,…,vn的物品和一个承重为W的背包,求这些物品中能够装到背包中的组合中最有价值的一种组合。
根据上述步骤进行求解:
- 定义状态DP[i,j]表示前i个物品装入承重j的背包中物品的最大价值
- 情况分析:
DP[i,j]有两种情况:
如果不放入第i个物品,则DP[i,j] = DP[i-1,j]
如果放入第i个物品,则DP[i,j] = DP[i-1,j-wi] + vi
(i才是主变量,转移方程是i的转移,所以第二种情况是i和i-1的关系,而不是和DP[i,j-1]的关系?)
(换一种理解,如果装进去第i件商品,那么装入之前是什么状态,肯定是V(i-1,j-w(i))啊) - 因此得出递推关系
DP[i,j] = max{DP[i-1,j], DP[i-1,j-wi] + vi}
- 进行初始化:
DP[0,j]=DP[i,0]=0
- 进行求解
代码表示
# w[i]表示第i个物品的重量, v[i]表示第i个物品的价值, 下标从V开始
# S表示背包总空间, N表示总共多少个物品
Algorithm bag:
# N+1行S+1列
dp = [[0 for i in range(S + 1)] for j in range(N + 1)]
for i in range(1, N+1):
for j in range(1, S+1):
if j > w[i]: # 判断此时背包空间是否大于此时物品重量
dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]] + v[i])
else:
dp[i][j] = dp[i-1][j]
# 返回在空间为S时前N个物品的最大价值
return dp[N][S]
四、最优二叉查找树
4.1 什么是二叉查找树
二叉查找树(BST:Binary Search Tree)是一种特殊的二叉树,它改善了二叉树节点查找的效率。二叉查找树有以下性质:
对于任意一个节点 n,
-
其左子树(left subtree)下的每个后代节点(descendant node)的值都小于节点 n 的值;
-
其右子树(right subtree)下的每个后代节点的值都大于节点 n 的值。
4.2 什么是最优二叉查找树
给定n个互异的关键字组成的序列K=<k1,k2,…,kn>,且关键字有序(k1<k2<…<kn),我们想从这些关键字中构造一棵二叉查找树。
对每个关键字ki,一次搜索搜索到的概率为pi。
可能有一些搜索的值不在K内,因此还有n+1个“虚拟键”D=<d0,d1,…,dn>,他们代表不在K内的值。
具体为:d0代表所有小于k1的值,dn代表所有大于kn的值。而对于i = 1,2,…,n-1,虚拟键di代表所有位于ki和ki+1之间的值。
对于每个虚拟键,一次搜索对应于di的概率为qi。
要使得查找一个节点的期望代价(代价可以定义为:比如从根节点到目标节点的路径上节点数目)最小,就需要建立一棵最优二叉查找树。
4.3 最优二叉树查找的期望代价
对于二叉查找树,期望代价表示如下
E ( T ) = ∑ i = 1 n ( depth ( k i ) + 1 ) ∗ p i + ∑ i = 0 n ( depth ( d i ) + 1 ) ∗ q i E(T)=\quad \sum_{i=1}^{n}\left(\operatorname{depth}\left(k_{i}\right)+1\right) * p_{i}+\sum_{i=0}^{n}\left(\operatorname{depth}\left(d_{i}\right)+1\right) * q_{i} E(T)=∑i=1n(depth(ki)+1)∗pi+∑i=0n(depth(di)+1)∗qi
当E(T)得到最小值的时候,则有最优二叉查找树
4.4 最优二叉查找树的最优子结构与动态规划
最优子结构:
如果一棵最优二叉查找树T有一棵包含关键字ki,…,kj的子树T’,那么这可子树T’对于关键字Ki,…,kj和虚拟键di-1,…dj的子问题也必定是最优的。
给定关键字ki,…,kj,假设kr(i<=r<=j)是包含这些键的一棵最优子树的根。其左子树包含关键字ki,…,kr-1和虚拟键di-1,…,dr-1,右子树包含关键字kr+1,…,kj和虚拟键dr,…dj。我们检查所有的候选根kr,就保证可以找到一棵最优二叉查找树。
根据动态规划的解决步骤:
- 定义
e[i,j]
表示包含关键字ki,…,kj和di-1,…,dj的最优二叉查找树的期望代价,最终的目的是求e[1,n]。
定义e[i,i-1]
表示只包含虚拟键di-1的子树(此时只为一个节点)的期望代价,e[i,i-1] = qi-1。
定义w[i,j]
表示从ki到kj的子树加上di-1到dj的概率总和,表示为 w [ i , j ] = ∑ l = i j p l + ∑ l = i − 1 j q l . w[i, j]=\sum_{l=i}^{j} p_{l}+\sum_{l=i-1}^{j} q_{l} . w[i,j]=∑l=ijpl+∑l=i−1jql. - 情况分析:
根据上述对子结构的分析,当j=i-1的时候,e[i,i-1]=qi-1
当j>=i的时候, 当一棵树成为另一个节点的子树的时候, 子树中每个节点的深度都加一,导致期望搜索代价增加了那个子树的概率总和,以kr为根的子树的期望代价为:
e [ i , j ] = 1 ∗ p r + ( e [ i , r − 1 ] + w [ i , r − 1 ] ) + ( e [ r + 1 , j ] + w [ r + 1 , j ] ) e[i, j]=1* p_{r}+(e[i, r-1]+w[i, r-1])+(e[r+1, j]+w[r+1, j]) e[i,j]=1∗pr+(e[i,r−1]+w[i,r−1])+(e[r+1,j]+w[r+1,j])
(新的期望=节点r的期望+(r左边子树的原期望+下移导致增加的期望)+(r右边子树的原期望+下移导致增加的期望)) - 因此得出递推公式
e [ i , j ] = { q i − 1 if j = i − 1 , min i ≤ r ≤ j { e [ i , r − 1 ] + e [ r + 1 , j ] + w ( i , j ) } if i ≤ j e[i, j]=\left\{\begin{array}{ll}q_{i-1} & \text { if } j=i-1, \\ \min _{i \leq r \leq j}\{e[i, r-1]+e[r+1, j]+w(i, j)\} & \text { if } i \leq j\end{array}\right. e[i,j]={qi−1mini≤r≤j{e[i,r−1]+e[r+1,j]+w(i,j)} if j=i−1, if i≤j
(对r求最小)
w [ i , j ] = ∑ l = i j p l + ∑ l = i − 1 j q l . w[i, j]=\sum_{l=i}^{j} p_{l}+\sum_{l=i-1}^{j} q_{l} . w[i,j]=∑l=ijpl+∑l=i−1jql.
(e[i,i]直接带入可知)
4.5 伪代码
首先先用一个直观的示意图来解释一下整个过程大概是什么样的:
- 每一轮是沿着左上到右下的斜对角线进行的
- 设一个变量d表示目前操作距离当前行距离
- 求e[i,j]的时候需要如图考虑k取不同值的情况
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aRWkjhjt-1618717259595)(https://gitee.com/DoctorDream/store/raw/master/image/2021/%E6%9C%80%E4%BC%98%20BST%E7%A4%BA%E6%84%8F%E5%9B%BE.png)]
# 动态规划求解最优二叉查找树
# 输入:一个包含n个键对应概率的数组P[1...n],一个包含n个虚拟键对应的概率数组Q[0...n]
# 输出:在最优BST中成功查找的平均比较次数,以及最优BST中子树的根表root
Algorithm OptimalBST(p[1...n],q[0...n]):
# 初始化保存根节点的root
root[n][n+1]
# 初始化只包括虚拟键的子树
for i ← 1 to n do:
e[i,i-1] ← q[i-1]
w[i,i-1] ← q[i-1]
# 沿着斜对角线向右边推进
for d ← 1 to n do:
for i ← 1 to n - d + 1:
j ← i + d - 1
e[i,j] ← 9999999
w[i,j] ← w[i,j - 1] + p[j] + q[j]
# 求r移动时取最小期望的时候
for k ← i to j do:
temp = e[i,k - 1] + e[k + 1,j] + w[i,j]
if temp < e[i,j]:
e[i,j] = temp
root[i][j] = k
return e[1,n],root