DP技术广泛应用于许多组合优化问题,比如图的多起点与多终点的最短路径问题,矩阵链的乘法问题,最大效益投资问题,背包问题,最长公共子序列问题,图像压缩问题,最大子段和问题,最优二分检索书树问题,RNA的最优二级结构问题等。
实践中的组合优化问题搜索空间往往比较大,由于中间有很多重复计算。DP技术通过划分子问题的边界,从小的子问题开始,逐层向上求解,通过子问题之间的依赖关系,有效利用前面已经得到的结果,最大限度减少重复工作,以提高算法效率。
一、动态规划的设计思想
1. 首先看下面这个多起点,多终点的例子:
我们首先想到的是蛮力算法,也就是穷举每一个起点到每一个终点的所有可能的路径,然后计算每条路径的长度,从中找出最短路径。
下面,用动态规划的算法来解决这个问题,从终点向起点推,使得前面求解出的问题恰好是后面问题的子问题,到最后一步求解出的最大的子问题正好是原始问题。
注:每个圆圈节点上方的标记表示为从终点到该节点的最小长度,u表示向上,d表示向下。
第一步:确定从任何C到T的路径长度。
第二步:确定从任何B到终点的路径长度。
递推的判断公式如下
例如:
类似地可确定从任何A,S到终点的路径长度。
递推的判断公式如下
总结:这种算法的好处是,在判断时只需要考虑前面子问题的最优解可能的延伸结果,从而把许多不可能成为最优解的部分路径今早从搜索中删除,从而能够提高效率。
2. 使用动态规划技术的必要条件
最优子结构性质:一个最优决策序列的任何子序列本身一定是相对于子序列的初始和结束状态的最优的决策序列
3. 动态规划的设计步骤
(1)划分子问题,用参数表达子问题的边界,将问题求解转变成多步判断的过程。
(2)确定优化函数,以该函数的极大(或极小)作为判断的依据,确定是否满足优化原则。
(3)列出关于优化函数的递推方程和边界跳进。
(4)考虑是否需要设立标记函数。
(5)自底向上计算,用表格存储中间结果。
(6)根据表格通过追溯给出最优解。
二、DP算法的典型应用
1. 矩阵相乘问题
设A1,A2,...,An为n个矩阵的阵列,其中Ai为Pi-1*Pi阶矩阵。
eg. P=<30,35,15,5,10,20>,相应的矩阵链是:A1,A2,A3,A4,A5。设计DP算法求得最小的运算次数。
Solve:
程序如下:
#include <iostream> #define INF 0x7fffffff using namespace std; int MatrixChain(int *p, int size) { int **dp; int i,j,k,win; dp = (int **)malloc(sizeof(int)*(size)); for(i=0; i<size+1; ++i) dp[i] = (int *)malloc(sizeof(int)*(size)); for(i=0; i<size; ++i) for(j=0; j<size; ++j) dp[i][j] = 0; // 迭代次数,链长为win for(win = 2; win < size; ++win) { //确定窗口的前边界 for(i=1; i< size-win+1; ++i) { j = i+win-1; //窗口的后边界 dp[i][j] = dp[i+1][j] + p[i-1]*p[i]*p[j]; for(k=i+1; k<j; ++k) { int temp = dp[i][k] + dp[k+1][j] + p[i-1]*p[k]*p[j]; if (temp < dp[i][j]) dp[i][j] = temp; } } } return dp[1][size-1]; } int main() { #ifdef LOCAL freopen("data.in","r",stdin); freopen("data.out","w",stdout); #endif int arr[] = {30,35,15,5,10,20}; int size = sizeof(arr)/sizeof(arr[0]); cout << MatrixChain(arr,size); return 0; }