动态规划问题:用来解决最优化问题
基本概念:将一个问题,分解成多个阶段来解决,每一个阶段的决策都依赖于当前的状态,决策过后状态又发生了转移,这种多阶段来解决最优化问题的过程就是动态规划。
基本思想与策略:基本思想与分治法类似,也是将带求解的问题分解为若干子问题(动态规划称之为阶段),按顺序求解子问题(子阶段),前一个子问题解,为后一个子问题的求解提供了信息。在求解任一个子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解(保留的这些局部解一般通过数组进行存储),丢弃其他局部解。依次解决各个子问题,最后一个子问题就是初始问题的解。
由于动态规划解决的问题多数有重叠子问题这个特点,为减少重复计算,对每一个子问题只解一次,将其不同阶段的不同状态保存到一个二维数组中。
与分治法最大的区别就是:适合于用动态规划求解的问题,经分解后得到的子问题往往不是互相独立的(即下一个阶段的求解是建立在上一个子问题的解的基础上的,从而进行进一步的求解)。
适用的情况
能用动态规划求解的问题一般要具有3个性质:
(1)、最优化原理:如果问题的最优解所包含的子问题的解也是最优的,就称为该问题的最优子结构,即满足最优化原理。
(2)、无后效性:即某阶段状态一旦确定,就不受这个状态的以后的决策影响,也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关。
(3)、有重叠子问题:即子问题之间是不独立的,一个子问题在一阶段的决策中肯能多次被用到。(该性质并不是动态规划所必需的条件,但是如果没有该性质,动态规划算法较其他算法没有优势)。
求解的基本步骤
动态规划所处理的问题是一个多阶段决策问题,一般由初始状态开始,通过对中间阶段决策的选择,达到结束状态。这些决策形成了一个决策序列,同时确定了完成整个过程的一条活动路线(通常是求最优的活动路线)。如图所示。动态规划的设计都有着一定的模式,一般要经历以下几个步骤。
初始状态→│决策1│→│决策2│→…→│决策n│→结束状态
图1 动态规划决策过程示意图
(1)划分阶段:按照问题的时间或空间特征,把问题分为若干个阶段。在划分阶段时,注意划分后的阶段一定要是有序的或者是可排序的,否则问题就无法求解。
(2)确定状态和状态变量:将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来。当然,状态的选择要满足无后效性。
(3)确定决策并写出状态转移方程:因为决策和状态转移有着天然的联系,状态转移就是根据上一阶段的状态和决策来导出本阶段的状态。所以如果确定了决策,状态转移方程也就可写出。但事实上常常是反过来做,根据相邻两个阶段的状态之间的关系来确定决策方法和状态转移方程。
(4)寻找边界条件:给出的状态转移方程是一个递推式,需要一个递推的终止条件或边界条件。
一般,只要解决问题的阶段、状态和状态转移决策确定了,就可以写出状态转移方程(包括边界条件)。
实际应用中可以按以下几个简化的步骤进行设计:
(1)分析最优解的性质,并刻画其结构特征。
(2)递归的定义最优解。
(3)以自底向上或自顶向下的记忆化方式(备忘录法)计算出最优值
(4)根据计算最优值时得到的信息,构造问题的最优解
算法实现的说明
动态规划的主要难点在于理论上的设计,也就是上面4个步骤的确定,一旦设计完成,实现部分就会非常简单。
使用动态规划求解问题,最重要的就是确定动态规划三要素:
(1)问题的阶段 (2)每个阶段的状态
(3)从前一个阶段转化到后一个阶段之间的递推关系。
递推关系必须是从次小的问题开始到较大的问题之间的转化,从这个角度来说,动态规划往往可以用递归程序来实现,不过因为递推可以充分利用前面保存的子问题的解来减少重复计算,所以对于大规模问题来说,有递归不可比拟的优势,这也是动态规划算法的核心之处。
确定了动态规划的这三要素,整个求解过程就可以用一个最优决策表来描述,最优决策表是一个二维表,其中行表示决策的阶段,列表示问题状态,表格需要填写的数据一般对应此问题的在某个阶段某个状态下的最优值(如最短路径,最长公共子序列,最大价值等),填表的过程就是根据递推关系,从1行1列开始,以行或者列优先的顺序,依次填写表格,最后根据整个表格的数据通过简单的取舍或者运算求得问题的最优解。
f(n,m)=max{f(n-1,m), f(n-1,m-w[n])+P(n,m)}
动态规划问题的解题框架
代码
for(j=1; j<=m; j=j+1) // 第一个阶段
xn[j] = 初始值;
for(i=n-1; i>=1; i=i-1)// 其他n-1个阶段
for(j=1; j>=f(i); j=j+1)//f(i)与i有关的表达式
xi[j]=j=max(或min){g(xi-1[j1:j2]), ......, g(xi-1[jk:jk+1])};
t = g(x1[j1:j2]); // 由子问题的最优解求解整个问题的最优解的方案
print(x1[j1]);
for(i=2; i<=n-1; i=i+1)
{
t = t-xi-1[ji];
for(j=1; j>=f(i); j=j+1)
if(t=xi[ji])
break;
}
以上借鉴:http://www.cnblogs.com/steven_oyj/archive/2010/05/22/1741374.html
一个简答的例子:
哈里波特终于离开了可怕的舅舅家,来到了Hogwarts的魔法学校学习魔法。魔法的世界是多么的神奇,小哈利对一切都充满了好奇。他想尽可能多的学习魔法。现在的问题是有些魔法课的时间有冲突,哈利无法在一天内上所有的魔法课。所以需要你写一个程序来帮助哈里波特制定一个学习计划,来安排第一天的学习,使得他能尽可能地上更多的魔法课。注意,上课的时间是不能改变的。而且上课的时候不能迟到也不能早退,否则魔法老师会对哈利产生不满。可以假设从一个教室到另一个教室的时间短得忽略不计。另外,在Hogwarts的魔法世界里,是不使用24小时制的计时方法的,它只是简单的使用一个整数来表示当时的时间。
输入:每个测试数据开头是一个整数n(1<=n<=1000),表示魔法课的总数。接下来n行,每行包括两个正整数s、t,
分别表示该魔法课的上课时间和下课时间。其中s
输出:对于每个测试数据,在单独一行内输出哈利所能上的最多魔法课数。
样例:
输入/输出:
3
1 15
2 19
15 17
2
#include
using namespace std;
#define N 1010
int lesson[N][2]; //记录开课时间 与 下课时间
int num[N]; // 记录上第N堂课的话可上的最大课程
int main()
{
int n;
int i,j;
cin>>n;
for(i=1;i<=n;i++)
cin>>lesson[i][0]>>lesson[i][1]; //输入开课时间与课程结束时间
memset(num,0,sizeof(num));
num[n] = 1; //第一阶段,首先上最后一堂课的话,最大上课数为 1
int max1;
for(i=n-1;i>=1;i--) //从后往前遍历
{
max1 = 0;
for( j = i+1;j<=n;j++) //遍历从 i+1 堂课 到最后一场课 (动态规划,求解局部最优)
{
if(lesson[i][1] <= lesson[j][0] && max1 i)的开始时间
max1 = num[j];
}
//已经找到了 上过第i场课之后 还能上的课的最大值
num[i] = max1+1 ;
}
for(max1=0,i=1;i<=n;i++)
if(max1 < num[i])
max1 = num[i];
cout<