动态规划复习

引入:前面讲的分治法是将原问题分解成若干个规模较小,形式相同的子问题,然后在治理求解这两个子问题,然后再将子问题的解合并成一个问题得到原问题的解。但是,在分治法中每个格子问题是互不相交的,即相互独立的,如果出现子问题重叠,分治法就是重复求解了很多子问题,并不能体现分治的优势,降低了算法效率。
动态规划闪亮登场!
算法思想:动态规划也是一种分治思想,但是分治算法不同的是,分治算法是把原问题分解成若干规模较小,形式相同的子问题,自顶向下求解各个子问题,合并子问题的解最终得到原问题的解。而动态规划是将原问题分解为若干子问题,然后自底向上,先求解最小的子问题,把结果存储在表格中,再求解大的子问题时,直接从表格中查询小的子问题的解,避免重复计算,从而提高算法效率。
动态规划的两个性质:
1、最优子结构(基本条件)
最优子结构指的是问题的最优解包含其子问题的最优解。
2、子问题重叠(优势,不是必要条件)
子问题重叠是指在求解子问题的过程中,有大量的子问题时重复的,那么只需要求解一次,然后把结果存储在表中,以后使用时可以直接查询,不需要再次求解。
动态规划的解题步骤
(1)分析最优解的结构特征(分析该问题是否具有最优子结构的特征)
(2)建立最优值的递归式
(3)自底向上计算最优值,并记录
(4)构造最优解

最长公共子序列

【问题描述】字符序列的子序列是指从给定字符序列中随意地(不
一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列。
令给定的字符序列X=(x1,…,xm),序列Y=(y1,…,yk)是X的子
序列,存在X的一个严格递增下标序列(i1,…,ik),使得对所有的
j=1,…,k,有 xi=yj。

在这里插入图片描述
定义二维动态规划数组dp,其中dp[i][j]为子序列(a1,…,ai)和
(b1,…,bj)的最长公共子序列的长度。
每考虑字符a[i]或b[j]都为动态规划的一个阶段(共经历约m×n个阶
段)。
求解最长公共子序列情况说明:
请添加图片描述
在这里插入图片描述
那么如何从dp->lcs?
首先一个表格:
请添加图片描述
现有X6{A,C,A,B,D,F}和Y6{B,C,A,D,G,F},求他俩的最长公共子序列。
   解:我们采用填表的方式解决,表中填的数据为两个,分别是dp[i,j]和↑、←和↖

  dp[i,j]的值可以通过上述的公式得出,而“箭头”方向的选择则是通过下面的条件进行选择:
   1. xi=yj,用↖
   2. xi != yj时,dp[i,j]=max(dp[i-1,j],dp[i,j-1])
	   如果dp[i,j-1]>dp[i-1,j],用←,否则用↑
	   如果dp[i,j-1]<dp[i-1,j],用↑,否则用←
	   若都是0,则用↑

算法设计步骤:
(1)确定数据结构
(2)初始化
(3)循环阶段
(4)构造最优解
填表格
在这里插入图片描述
填完表格之后,我们从最后一格根据箭头方向一直往前走。途中↖箭头的记录下来就是最长公共子序列了。
在这里插入图片描述
得到的最长公共子序列就是{C,A,D,F}了。
也就是路径上第一个出现更新数字的字符。

算法复杂度分析:
(1)时间复杂度:由于每个数组单元的计算耗费Ο(1)时间, 如果两个字符串的长度分别是m、n,那么算法时间复杂度为 Ο(mn)。
(2)空间复杂度:空间复杂度主要为两个二维数组c[][], b[][],占用的空间为O(mn)。

最优解输出函数。输出最优解要使用倒推法。因为我们在求最长公共子序列长度c[i][j]的过程中,用b[i][j]记录了c[i][j]的来源,则可以根据b[i][j]倒退最优解
两串下标从0开始,串s1长度为len1,串s2长度为len2,int c[i][j],b[][]
初始化,行列为0

void init(){
for(int i = 0;i<=len1;i++){
c[0][i] = 0;
}
for(int i = 0;i<=len2;i++){
c[i][0] = 0;
}
}

最长公共子序列主函数LCS

void LCS(){
	int i,j;
	for(i = 1;i<=len1;i++){
		for(j = 1;j<=len2;j++){
			if(s[i-1] = s[j-1]){
			b[i][j] =1;
			}else{
				if(c[i][j-1] >= c[i-1][j]){
					c[i][j] = c[i][j-1];
					b[i][j] = 2;
				}
				if(c[i][j-1] <= c[i-1][j]){
					c[i][j] = c[i-1][j];
					b[i][j] = 3;
				}
			}
	}
}

输出print函数

void print(int i,int j){
	if(i == 0 ||j == 0) return;
	if(b[i][j] == 1){
		print(i-1,j-1);
		cout<<s1[i-1];
	}else if(b[i][j] == 2){
		print(i-1,j);
	}else if(b[i][j] == 3){
		print(i,j-1);
	}
}
矩阵连乘问题

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
矩阵连乘问题
就是对于给定n个连乘的矩阵,找出一种加括号的方法,是的矩阵连乘的计算量最小。原问题的最优解包含子问题的最优解。因此矩阵连乘问题具有最优子结构性质,采用动态规划求解。
采用动态规划方法求解的步骤:
(1)分析最优解的结构特征
因此矩阵连乘问题具有最优子结构性质,采用动态规划求解。
(2)建立最优值递归式
i = j时,只有一个矩阵,m[i][j] = 0; m[i][j]代表最优值
i< j ,m[i][j] = min{m[i][k]+m[k+1][j]+p[i-1]*p[k+1]*p[j]}
(3)自底向上计算并且记录最优值
先求两个矩阵相乘的最优值,再求3个矩阵相乘的最优值,知道n个矩阵相乘的最优值
(4)构造最优解
上面得到的最优值只是矩阵连乘的最小的乘法次数,并不知道加括号的次序,需要从表中还原加括号的次序

0 1背包问题

【问题描述】有n个重量分别为{w1,w2,…,wn}的物品,它们的价
值分别为{v1,v2,…,vn},给定一个容量为W的背包。
设计从这些物品中选取一部分物品放入该背包的方案,每个物品
要么选中要么不选中,要求选中的物品不仅能够放到背包中,而且重量
和为W具有最大的价值。
【问题求解】对于可行的背包装载方案,背包中物品的总重量不能
超过背包的容量。
最优方案是指所装入的物品价值最高,即 v1x1+v2x2+v3x3+vixi(x的取值为0或1,1表示选取物品i)取得最大值。
在该问题中需要确定x1、x2、…、xn的值。假设按i=1,2,…,n的
次序来确定xi的值,对应n次决策即n个阶段
算法设计
有n个物品,每个物品的重量为w[i],每个物品的价值为v[i],购物车的容量为r。选若干物品放入购物车上,使得购物车中总价值最大。
(1)确定合适的数据结构
采用一维数组w[i],v[i]记录第i个物品的重量和价值,二维数组dp[i][j]来记录前i个物品放入容量为j的购物车的最大价值。
(2)初始化:
初始化dp[i][0],dp[0][j]值为0,
(3)循环阶段
递推式:
dp[i][j] = { 0,i=0或者r = 0
dp[i-1][r] ,r<w[i]
max{dp[i-1][r],dp[i-1][r-w[i]]+v[i] }
}
根据递推式计算前i个物品放入容量为j的购物车的最大价值

for(int i = 1;i <= n;i++){    //n个物品
	for(int j = 1;j<=W;j++){  //容量从1-j递增
		if(j<w[i])
			dp[i][j] =dp[i-1][j];
		else
			dp[i][j] = max(dp[i-1][j],dp[i-1][r-w[i]]+v[i])
	}
}
cout<<"装入购物车最大容量是"<<c[n][w]<<endl;

(4)构造最优解
根据dp[i][j]逆向构造最优解。
若dp[i][j] = dp[i-1][j] 说明第i个物品没有放入购物车中,x[i] = 0,令i = i-1,继续查找,反之则是物品放入购物车,x[i]=1,令i = i-1,j = j-w[i],继续查找。最终得到解向量x[i]=1的i值

for(i = n;i >= 0;i--){
		if(dp[i][j] != dp[i-1][j]){
			x[i] = 1;
			j-=w[i];
		}
		else
			x[i] = 0;
}
for(i = 1;i<=n;i++){
	if(x[i] == 1)
		cout<<x[i];
}

算法复杂度分析
算法中主要是嵌套两层for循环,时间复杂度为O(nr)
运用到了二维数组dp[][],空间复杂度为O(n
r)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值