算法设计与分析——动态规划

算法思维导图

在这里插入图片描述

概念辨析

动态规划算法是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推(或者说分治)的方式去解决。
动态规划算法的基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。

基本要素

  1. 最优子结构性质
    当问题的最优解包含了其子问题的最优解时,称该问题具有最优子结构性质。
    在分析问题的最优子结构性质时,所用的方法具有普遍性。首先假设由问题的最优解导出的其子问题的解不是最优的,在设法说明在这个假设下可构造出比原问题最优解更好的解,从而导致矛盾。
  2. 重叠子问题性质
    在用递归算法自顶向下解此问题时,每次产生的子问题并不总是新问题,有些子问题被重复计算。动态规划正是利用了这种子问题的重叠性质,对每个子问题只解一次,然后将其解保存在一个表格中,当再次需要解此子问题时,只是简单地用常数时间查看一下结果。

算法的步骤

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

经典范例

1、矩阵连乘问题
【问题描述】

给定n个矩阵{A1,A2,…,An},其中Ai与Ai+1是可乘的,i=1,2…,n-1。如何确定计算矩阵连乘积的计算次序,使得依此次序计算矩阵连乘积需要的数乘次数最少。例如,给定三个连乘矩阵{A1,A2,A3}的维数分别是10100,1005和550,采用(A1A2)A3,乘法次数为101005+10550=7500次,而采用A1(A2A3),乘法次数为100550+10100*50=75000次乘法,显然,最好的次序是(A1A2)A3,乘法次数为7500次。

分析:
矩阵链乘法问题描述:
给定由n个矩阵构成的序列{A1,A2,…,An},对乘积A1A2…An,找到最小化乘法次数的加括号方法。
1)寻找最优子结构
此问题最难的地方在于找到最优子结构。对乘积A1A2…An的任意加括号方法都会将序列在某个地方分成两部分,也就是最后一次乘法计算的地方,我们将这个位置记为k,也就是说首先计算A1…Ak和Ak+1…An,然后再将这两部分的结果相乘。
最优子结构如下:假设A1A2…An的一个最优加括号把乘积在Ak和Ak+1间分开,则前缀子链A1…Ak的加括号方式必定为A1…Ak的一个最优加括号,后缀子链同理。
一开始并不知道k的确切位置,需要遍历所有位置以保证找到合适的k来分割乘积。
2)构造递归解
设m[i,j]为矩阵链Ai…Aj的最优解的代价,则
在这里插入图片描述
3)构建辅助表,解决重叠子问题
从第二步的递归式可以发现解的过程中会有很多重叠子问题,可以用一个nXn维的辅助表m[n][n] s[n][n]分别表示最优乘积代价及其分割位置k 。
辅助表s[n][n]可以由2种方法构造,一种是自底向上填表构建,该方法要求按照递增的方式逐步填写子问题的解,也就是先计算长度为2的所有矩阵链的解,然后计算长度3的矩阵链,直到长度n;另一种是自顶向下填表的备忘录法,该方法将表的每个元素初始化为某特殊值(本问题中可以将最优乘积代价设置为一极大值),以表示待计算,在递归的过程中逐个填入遇到的子问题的解。
代码:

#include<iostream>
using namespace std;
const int N = 100;
int A[N];//矩阵规模
int m[N][N];//最优解
int s[N][N];
void MatrixChain(int n)
{
	int r, i, j, k;
	for (i = 0; i <= n; i++)//初始化对角线
	{
		m[i][i] = 0;
	}
	for (r = 2; r <= n; r++)//r个矩阵连乘
	{
		for (i = 1; i <= n - r + 1; i++)//r个矩阵的r-1个空隙中依次测试最优点
		{
			j = i + r - 1;
			m[i][j] = m[i][i]+m[i + 1][j] + A[i - 1] * A[i] * A[j];
			s[i][j] = i;
			for (k = i + 1; k < j; k++)//变换分隔位置,逐一测试
			{
				int t = m[i][k] + m[k + 1][j] + A[i - 1] * A[k] * A[j];
				if (t < m[i][j])//如果变换后的位置更优,则替换原来的分隔方法。
				{
					m[i][j] = t;
					s[i][j] = k;
				}
			}
		}
	}
}
void print(int i, int j)
{
	if (i == j)
	{
		cout << "A[" << i << "]";
		return;
	}
	cout << "(";
	print(i, s[i][j]);
	print(s[i][j] + 1, j);//递归1到s[1][j]
	cout << ")";
}
int main()
{
	int n;//n个矩阵
	cin >> n;
	int i, j;
	for (i = 0; i <= n; i++)
	{
		cin >> A[i];
	}
	MatrixChain(n);
	cout << "最佳添加括号的方式为:";
	print(1, n);
	cout << "\n最小计算量的值为:" << m[1][n] << endl;
	return 0;
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值