1、基本概念算法
动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法。20世纪50年代初美国数学家R.E.Bellman等人在研究多阶段决策过程(multistep decision process)的优化问题时,提出了著名的最优化原理(principle of optimality),把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解,创立了解决这类过程优化问题的新方法——动态规划。1957年出版了他的名著《Dynamic Programming》,这是该领域的第一本著做。编程
动态规划过程是:每次决策依赖于当前状态,又随即引发状态的转移。一个决策序列就是在变化的状态中产生出来的,因此,这种多阶段最优化决策解决问题的过程就称为动态规划。数组
2、基本思想与策略网络
基本思想与分治法相似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各类可能的局部解,经过决策保留那些有可能达到最优的局部解,丢弃其余局部解。依次解决各子问题,最后一个子问题就是初始问题的解。框架
因为动态规划解决的问题多数有重叠子问题这个特色,为减小重复计算,对每个子问题只解一次,将其不一样阶段的不一样状态保存在一个二维数组中。优化
与分治法最大的差异是:适合于用动态规划法求解的问题,经分解后获得的子问题每每不是互相独立的(即下一个子阶段的求解是创建在上一个子阶段的解的基础上,进行进一步的求解)。spa
区别:.net
(1)动态规划和分治区别:设计
动态规划算法:它一般用于求解具备某种最优性质的问题。在这类问题中,可能会有许多可行解。每个解都对应于一个值,咱们但愿找到具备最优值的解。动态规划算法与分治法相似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,而后从这些子问题的解获得原问题的解。与分治法不一样的是,适合于用动态规划求解的问题,经分解获得子问题每每不是互相独立的。code
分治法:若用分治法来解这类问题,则分解获得的子问题数目太多,有些子问题被重复计算了不少次。若是咱们可以保存已解决的子问题的答案,而在须要时再找出已求得的答案,这样就能够避免大量的重复计算,节省时间。咱们能够用一个表来记录全部已解的子问题的答案。
注:无论该子问题之后是否被用到,只要它被计算过,就将其结果填入表中。这就是动态规划法的基本思路。
3、适用的状况
能采用动态规划求解的问题的通常要具备3个性质:
(1) 最优化原理:若是问题的最优解所包含的子问题的解也是最优的,就称该问题具备最优子结构,即知足最优化原理。
(2) 无后效性:即某阶段状态一旦肯定,就不受这个状态之后决策的影响。也就是说,某状态之后的过程不会影响之前的状态,只与当前状态有关。
(3)有重叠子问题:即子问题之间是不独立的,一个子问题在下一阶段决策中可能被屡次使用到。(该性质并非动态规划适用的必要条件,可是若是没有这条性质,动态规划算法同其余算法相比就不具有优点)
4、求解的基本步骤
动态规划所处理的问题是一个多阶段决策问题,通常由初始状态开始,经过对中间阶段决策的选择,达到结束状态。这些决策造成了一个决策序列,同时肯定了完成整个过程的一条活动路线(一般是求最优的活动路线)。如图所示。动态规划的设计都有着必定的模式,通常要经历如下几个步骤。
初始状态→│决策1│→│决策2│→…→│决策n│→结束状态
图1 动态规划决策过程示意图
(1)划分阶段:按照问题的时间或空间特征,把问题分为若干个阶段。在划分阶段时,注意划分后的阶段必定要是有序的或者是可排序的,不然问题就没法求解。
(2)肯定状态和状态变量:将问题发展到各个阶段时所处于的各类客观状况用不一样的状态表示出来。固然,状态的选择要知足无后效性。
(3)肯定决策并写出状态转移方程:由于决策和状态转移有着自然的联系,状态转移就是根据上一阶段的状态和决策来导出本阶段的状态。因此若是肯定了决策,状态转移方程也就可写出。但事实上经常是反过来作,根据相邻两个阶段的状态之间的关系来肯定决策方法和状态转移方程。
(4)寻找边界条件:给出的状态转移方程是一个递推式,须要一个递推的终止条件或边界条件。
通常,只要解决问题的阶段、状态和状态转移决策肯定了,就能够写出状态转移方程(包括边界条件)。
实际应用中能够按如下几个简化的步骤进行设计:
(1)分析最优解的性质,并刻画其结构特征。
(2)递归的定义最优解。
(3)以自底向上或自顶向下的记忆化方式(备忘录法)计算出最优值
(4)根据计算最优值时获得的信息,构造问题的最优解
5、算法实现的说明
动态规划的主要难点在于理论上的设计,也就是上面4个步骤的肯定,一旦设计完成,实现部分就会很是简单。
使用动态规划求解问题,最重要的就是肯定动态规划三要素:
(1)问题的阶段 (2)每一个阶段的状态
(3)从前一个阶段转化到后一个阶段之间的递推关系。
递推关系必须是从次小的问题开始到较大的问题之间的转化,从这个角度来讲,动态规划每每能够用递归程序来实现,不过由于递推能够充分利用前面保存的子问题的解来减小重复计算,因此对于大规模问题来讲,有递归不可比拟的优点,这也是动态规划算法的核心之处。
肯定了动态规划的这三要素,整个求解过程就能够用一个最优决策表来描述,最优决策表是一个二维表,其中行表示决策的阶段,列表示问题状态,表格须要填写的数据通常对应此问题的在某个阶段某个状态下的最优值(如最短路径,最长公共子序列,最大价值等),填表的过程就是根据递推关系,从1行1列开始,以行或者列优先的顺序,依次填写表格,最后根据整个表格的数据经过简单的取舍或者运算求得问题的最优解。
f(n,m)=max{f(n-1,m), f(n-1,m-w[n])+P(n,m)}
6、动态规划算法基本框架
1 for(j=1; j<=m; j=j+1) // 第一个阶段
2 xn[j] = 初始值;
3
4 for(i=n-1; i>=1; i=i-1)// 其余n-1个阶段
5 for(j=1; j>=f(i); j=j+1)//f(i)与i有关的表达式
6 xi[j]=j=max(或min){g(xi-1[j1:j2]), ......, g(xi-1[jk:jk+1])};
8
9 t = g(x1[j1:j2]); // 由子问题的最优解求解整个问题的最优解的方案
10
11 print(x1[j1]);
12
13 for(i=2; i<=n-1; i=i+1)
15 {
17 t = t-xi-1[ji];
18
19 for(j=1; j>=f(i); j=j+1)
21 if(t=xi[ji])
23 break;
25 }
例 最短路径问题
如图,给定一个运输网络,两点之间连线上的数字表示两点间的距离。试求一条从A到E的运输路线,使总距离最短。
从图中能够看出,咱们能够把从A到E的过程分红若干个阶段,这里是四个阶段。处于每一个阶段时,都要选择走哪条支路——决策,一个阶段的决策除了影响该阶段的效果以外,还影响到下一阶段的初始状态,从而也就影响到整个过程之后的进程。所以,在进行某一阶段的决策时,就不能只从这一阶段自己考虑,而应使总体的效果最优。
咱们能够从最后一个阶段开始,由终点向始点方向逐阶递推,寻找各点到终点的最短路径,当递推到始点时,即获得了从始点到终点的全过程最短路。这种由后向前的递推方法,正是动态规划的寻优思想。

下面咱们对这个问题进行求解。把从A到E的全过程分为四个阶段,用k表示阶段变量。第一阶段,有一个初始状态A,三条可供选择的支路
、
、
,以此类推。咱们用
(
,
)表示在第k阶段由初始状态
到下阶段的初始状态
的支路距离。用
(
)表示从第k阶段的
到终点E的最短距离。
用逆序递推的方法:
1.阶段k = 4
第4阶段有两个初始状态
和
。若全过程最短路径通过
,则有
(
)= 4 ;若全过程最短路径通过
,则有
(
)= 3 。
2.阶段 k = 3
假设全过程最短路径在第3阶段通过
点:
若由
,则有
(
,
)+
(
)= 4 + 4 = 8
若由
,则有
(
,
)+
(
)= 6 + 3 = 9
所以,
(
)= min(8,9)= 8 ,即由
的最短路径是
,最短距离是8。
相似地,假设全过程最短路径通过
点,则有
(
)= min{[
(
,
)+
(
)],[
(
,
)+
(
)]}
= min (7,8) = 7
即由
的最短路径是由
,最短距离是7。
同理可得出:
(
)= min ( 6 , 6 ) = 6
由
的最短路径有两条
和
,其距离都是6。
3.阶段 k = 2
相似地,可计算
、
、
以下:
= min( 15 , 14 , 14 ) = 14
= min(11, 12 , 12 ) = 11
= min(14 , 15 , 13 ) = 13
所以,由
的最短路径有三条
、
、
,最短距离都是14;由
的最短路径是
,距离是11;由
的最短路径有两条:
和
,距离是13。
4.阶段 k = 1
= min ( 16 , 15 , 16 ) = 15
所以,由
的全过程最短路径是
,最短距离是15。
从以上过程能够看出,每一个阶段中,都求出本阶段的各个初始状态到终点E的最短路径和最短距离,当逆序递推到过程始点A时,便获得全过程的最短路径及其最短距离,同时获得一族最优结果(即各阶段的各状态到终点E的最优结果)。和穷举法相比,逆叙递推方法大大减小了计算量,且大大丰富了计算结果。
此题也能够用顺序递推的方法求解,解法过程类似,在此就不赘述了。
例:求子数组之和的最大值
一个有N个元素的一维数组(a[0], a[1]….a[n-1]),咱们定义连续的a[i] ~ a[j],0<= i, j <=n-1为子数组。
显然这个数组中包含不少子数组,请求最大的子数组之和。
若是不想时间复杂度,用遍历全部可能子数组,而后找出最大值就能够了。
如今若是要求时间复杂度最小,那么确定是要DP解的。
咱们假设定义两个数组:
all[i]:表示从i~n-1,最大的子数组之和。
start[i]:表示包含i,而且从i~n-1,最大子数组之和。
all[i]中max只有三种可能:
(1) a[i]单独就是最大,以后再加一个就会变小。
(2)a[i]+…a[j]最大,即start[i]最大
(3)a[x]+..a[j]最大,即不包含i的后序某一个子数组和最大。
最终,最大的子数组之和是all[0]。根据上述3个可能,很容易写出以下递推式:
start[i] = max (a[i], a[i]+start[i+1])
all[i] = max(start[i], all[i+1])
注意咱们把上面max(a, b, c)拆成了两个max(a, b)
因为咱们在计算start[i]/all[i]时候须要start[i+?]的值,因此咱们从后向前递推dp。
代码以下,时间复杂度O(n):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int max(int a, int b)
{
if(a>b)
{
return a;
}else
{
return b;
}
}
int max_sum(int* arr, int n)
{
// Helper array
int i;
int* start = (int*)malloc(sizeof(int)*n);
int* all = (int*)malloc(sizeof(int)*n);
int final;
if(!start || !all)
{
return -1;
}
memset(start, 0, sizeof(int)*n);
memset(all, 0, sizeof(int)*n);
// dp
start[n-1] = arr[n-1];
all[n-1] = arr[n-1];
for(i=n-2;i>=0;i--)
{
start[i] = max(arr[i], arr[i]+start[i+1]);
all[i] = max(start[i], all[i+1]);
}
final = all[0];
// Free helper array
free(start);
free(all);
return final;
}
int main()
{
//int arr[6] = {1, -2, 3, 5, -3, 2}; // 8
int arr[6] = {0, -2, 3, 5, -1, 2}; // 9
//int arr[5] = {-9, -2, -3, -5, -3}; // -2
printf("max sum of sub_arr: %d \n", max_sum(arr, sizeof(arr)/sizeof(int)));
return 0;
}
来源:http://blog.csdn.net/cangchen/article/details/45044811