【算法分析与设计】第七章-动态规划法

动态规划是一种解决最优化问题的策略,通过保存子问题的最优解避免重复计算。与分治法和贪心法不同,动态规划适用于子问题重叠的情况,如0/1背包、最长公共子序列(LCS)等问题。动态规划法常用于多段图的最短路径计算和矩阵连乘的最小次数求解。
摘要由CSDN通过智能技术生成

一、知识铺垫

最优子结构特性
对于一个问题,如果能从子问题的最优解求得较大规模同类子问题的最优解,最终得到给定问题的最优解,这就是问题最优解的最优子结构特性。当一个问题的最优解中包含了子问题的最优解时,则称该问题具有最优子结构特性。 请注意区分最优解与最优解值

二、什么是动态规划

       动态规划是一种求解最优化问题的策略。动态规划通顾自底向上的方式,先求出并保存子问题的最优解,后面求解大规模的问题时可以直接把子问题的最优解拿来用,减少了重复计算。

分治法与动态规划

       动态规划的本质也是将规模较大的问题分解为规模较小的同类子问题。但分治法的子问题相互独立,可能会产生重复计算。动态规划法解决了这种问题。一个典型的例子就是Fibnacci数列。

f(n) = f(n - 1) + f(n - 2)

使用分治法递归求解时会产生重复计算,而使用记忆化搜索(动态规划的变种)会把子问题的解保留下来,保证了子问题的解只被计算一次。

贪心法与动态规划

       贪心法与动态规划都常用于求解最优化问题。贪心法不能保证一定能求得最优解,但可以求出近似解,比如0/1背包问题。当最优量度标准不存在时,贪心便求不出最优解。此时动态规划可以进行求解。因为贪心法每一步决策其实并不依赖于子问题的解,而依赖于求解完子问题时产生的状态。比如部分背包问题:贪心法进行决策时不会考虑子问题的解,只考虑求解子问题导致的当前状态(背包容量,剩余物品数目)。而动态规划法的每一步决策依赖于子问题的解。

三、动态规划法的使用场景

常用但不限于求解最优化问题。非最优化问题:青蛙跳阶、Fibonacci等也可用DP求解,这不属于最优化问题,但问题的解依赖于子问题的解。最优化问题如:0/1背包,LCS,LIS,多段图,floyd, 矩阵连乘,最优二叉树等等。

四、 ★问题求解步骤★

  1. 判断问题是否满足最优性原理
  2. 根据题目描述确定状态表示(一维、二维),写出状态转移方程
  3. 明确basecase,自底向上求最优解值
  4. 根据最优解值求出最优解

五、典例

  1. 多段图问题, O(e)
  • 向前递推
    状态转移方程:

cost(i, j) = min{cost(i + 1, p) + c(j , p)},i代表Vi组,j是该组内的顶点,cost(i,j)表示第i组的j号点到汇点的最短距离。
可以省去维度 i。

int MultGraph(int n){
	for(int i = n - 2; i >= 0; i --){
		cost[i] = INF;
		for(auto p = a[i]; p; p=p->next){
			int j = p->adjvex;
			cost[i] = min(cost[i],cost[j] + p->weight); 
		}
	}
	return cost[0];
}
  • 向后递推

cost(i , j) = min{cost(i - 1, p) + c(p, j)},cost(i, j)表示j到源点的最短距离

int MultGraph(int n){
	memset(cost, 0x3f, sizeof cost);
	cost[0] = 0;
	for(int i = 0; i <= n - 2; i ++){
		for(auto p = a[i]; p; p = p->next){
			int j = p->adjvex;
			cost[j] = min(cost[j],cost[i] + p->weight); 
		}
	}
	return cost[n - 1];
}

2.矩阵连乘问题
典型区间DP, O ( n 3 ) O(n^3) O(n3)
状态转移方程

m(i, j) = min{ m(i , k) + m(k + 1, j) + p i p k + 1 p j p_ip_{k+1}p_j pipk+1pj}, m(i, j)表示矩阵 A i . . . A j A_i...A_j Ai...Aj相乘的最小次数

int MatrixChain(int n){		// 动态规划法 
	for(int k = 2; k <= n; k ++){	// 枚举区间长度
		for(int i = 0; i + k - 1 < n; i ++){
			int j = i + k - 1;	//区间右端点
			m[i][j] = m[i][i] + m[i+1][j] + p[i]*p[i+1]*p[j+1];	// 参照元 k = i
			for(int k = i + 1; k < j; k ++){	// 区间分割点
				m[i][j] = min(m[i][j], m[i][k]+m[k+1][j] + p[i]*p[k+1]*p[j+1]);
			}	
		} 
	}
	return m[0][n-1];
}
int solve2(int i, int j){	// 备忘录方法 
	if(i == j) return 0;
	if(m[i][j]) return m[i][j];
	m[i][j] = solve2(i+1,j) + p[i]*p[i+1]*p[j+1];
	for(int k = i + 1; k < j; k ++){
		m[i][j] = min(m[i][j],solve2(i, k) + solve2(k+1,j) + p[i]*p[k+1]*p[j+1]);
	}
	return m[i][j];
}
  1. 最长公共子序列(LCS)
    O(mn)
    状态转移方程:

if a[i] == b[j] then
f(i , j) = f(i - 1, j - 1) + 1;
else
f(i, j) = max{f(i - 1, j), f(i, j - 1)}

int LCS(int i, int j){
	if(i < 0 || j < 0) return 0;
	if(f[i][j]) return f[i][j];
	if(a[i] == b[j])
		return f[i][j] = 1 + LCS(i - 1, j - 1);
	else
		return f[i][j] = max(LCS(i-1,j), LCS(i, j - 1));
}
int LCS(){
	for(int i = 1; i <= n; i ++)
		for(int j = 1; j <= m; j ++){
			if(a[i] == b[j])
				f[i][j] = f[i-1][j-1] + 1;
			else
				f[i][j] = max(f[i-1][j], f[i][j-1]);
		}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值