最优性原理
对于一个具有n个输入的最优化问题,其求解过程往往可以划分为若干个阶段,每一阶段的决策依赖于前一阶段的状态,由决策所采取的动作使状态发生转移,成为下一阶段决策的依据。从而,一个决策序列在不断变化的状态中产生。这个决策序列产生的过程称为多阶段决策过程
。
算法总体思想
最优的局部解
,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。n=5时分治法计算斐波那契数的过程。
用参数表达
子问题的边界
,将问题求解转 变成多步判断的过程.
以该函数的
极大(或极小)
作为判断的依据,确定是否满足优化原则.
优化函数的递推方程 (或不等式)
和边界条件
自底向上
计算,以备忘录方法 (表格)存储中间结果下文以
矩阵连乘
举例
最优子结构
最优子结构性质
。自底向上的方式
递归地从子问题的最优解逐步构造出整个问题的最优解。最优子结构是问题能用动态规划算法求解的前提。使用动态规划技术的条件:
优化原则
一个最优决策序列的任何子序列本身一定是相对于子序列的初始和结束状态的最优的决策序列
反例:求总长模10的最小路径
重叠子问题
每次产生的子问题并不总是新问题
,有些子问题被反复计算多次。这种性质称为子问题的重叠性质
。只需要多项式时间
,从而获得较高的解题效率。代码:非递归实现
public static void matrixChain(int [] p, int [][] m, int [][] s)
{
int n=p.length-1;
for (int i = 1; i <= n; i++) m[i][i] = 0;
for (int r = 2; r <= n; r++) // r为计算的矩阵链长度
for (int i = 1; i <= n - r+1; i++) { //n-r+1为左边界
int j=i+r-1; // 右边界j
m[i][j] = m[i+1][j]+ p[i-1]*p[i]*p[j]; // Ai(Ai+1..Aj)
s[i][j] = i; //记录分割位置
for (int k = i+1; k < j; k++) {
int t = m[i][k] + m[k+1][j] + p[i-1]*p[k]*p[j]; //(Ai..Ak)(Ak+1..Aj)
if (t < m[i][j]) {
m[i][j] = t;
s[i][j] = k;}
}
}
}
算法复杂度分析:
算法matrixChain的主要计算量取决于算法中对r,i和k的3重循环。循环体内的计算量为O(1),而3重循环的总次数为O(n3)。因此算法的计算时间上界为O(n3)。算法所占用的空间显然为O(n2)。
代码:递归实现
// 子问题ij
public static int RecurMatrixChain(int i, int j) {
if(i==j) return 0;
int u = RecurMatrixChain(i+1,j) +p[i-1]*p[i]*p[j];//
s[i][j] = i;
// k划分
for(int k=i+1; k<j; k++){
// 递归地找最优的次数
int t = RecurMatrixChain(i, k) + RecurMatrixChain(k+1, j) + p[i-1]*p[k]*p[j];
if(t < u){
u = t;
// 找到更好的解
s[i][j] = k;
}
}
return u;
}
复杂性分析
复杂性高的原因:子问题重复计算
对于1≤i≤j≤n不同的有序对(i,j)对应于不同的子问题。因此,不同子问题的个数最多只有
由此可见,在递归计算时,许多子问题被重复计算多次
。这也是该问题可用动态规划算法求解的又一显著特征。
用动态规划算法解此问题,可依据其递归式以自底向上的方式进行计算
。在计算过程中,保存已解决的子问题答案
。每个子问题只计算一次,而在后面需要时只要简单查一下,从而避免大量的重复计算,最终得到多项式时间的算法
时间复杂性不同的原因:
备忘录方法的控制结构与直接递归方法的控制结构相同,区别在于备忘录方法
为每个解过的子问题建立了备忘录
以备需要时查看,避免了相同子问题的重复求解。
代码如下:
m<--0 //表示子问题还未被计算
private static int lookupChain(int i, int j)
{
if (m[i][j] > 0) return m[i][j];
if (i == j) return 0;
int u = lookupChain(i+1,j) + p[i-1]*p[i]*p[j];
s[i][j] = i;
for (int k = i+1; k < j; k++) {
int t = lookupChain(i,k) + lookupChain(k+1,j) + p[i-1]*p[k]*p[j];
if (t < u) {
u = t; s[i][j] = k;}
}
m[i][j] = u;
return u;
}
自底向上
,备忘录方法是自顶向下
,递归是自顶向下
的。每个子问题都要解一次
,但不会求解重复子问题;备忘录方法只解哪些确实需要解的子问题
;递归方法每个子问题都要解一次,包括重复子问题
。结束!