动态规划以及其与分治的对比
分治,是把一个问题分解成独立的子问题,递归的解决子问题,再组成原问题
动态规划是解决这些子问题不是独立的问题的情况。
斐波那契数列
其子问题不是独立的子问题
f(4)子问题是f(3)与f(2),而f(2)又是f(3)的子问题。
一个分而治之的算法做的工作比必要的多,反复地解决公共的子问题。
动态规划算法对应每个子问题只解决一次,然后将其答案保存在表中,从而避免了每次遇到子问题时重新计算答案的工作。
动态规划通常用于最优解问题
动态规划算法可以分为四个步骤。
1)最优解的递归结构
2)建立最优值的递推公式
3)计算最优值
4)构造最优解
1)——3)是必须的
只有需要求最优解是需要步骤4)
动态规划的要素
①最优子结构性质
②重叠子问题性质
1、最优子结构:当问题的最优解包含了其子问题的最优解时,称该问题具有最优子结构性质。
2、重叠子问题:在用递归算法自顶向下解问题时,每次产生的子问题并不总是新问题,有些子问题被反复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只解一次,而后将其解保存在一个表格中,在以后尽可能多地利用这些子问题的解。
矩阵连乘问题
考虑pq矩阵A,qr矩阵B
计算矩阵AB需要pqr次乘法
括号的位置不同会有不同的计算量。
idea1:brute force 穷举搜索法
对于n个矩阵的连乘积,设有p(n)个计算次序。我们可以在第k个和第k+1个矩阵之间将原矩阵划分为两个子矩阵序列,然后分别对这两个矩阵子序列完全加括号,最后对所得的结果加括号,则
计算次序太多了!!!穷举的太多
idea2:动态规划
step1:求最优解的递归结构
如果A[1:n]的一个最优解是A[1:k]和A[k+1:n]
那么A[1:k]和A[k+1:n]对于它们的子问题是最优的。
step2:建立最优值的递推公式
设m[i,j]是计算Ai……Aj乘积的最小次数
则:
情况 | m[i,j]值 |
---|---|
i=j | 0 |
i<j |
step3:计算最优值
①递归方法
public class Matrix_chainMultiplication {
public static void main(String args[]) {
int p[]= {10,100,5,50};
int n=p.length;
int s[][]=new int[n][n];
int u=recurMatriChain(1,n-1,p,s);
System.out.println(u);
for(int i=1;i<n;i++) {
for(int j=1;j<n;j++) {
System.out.print(s[i][j]+"\t");
}
System.out.println("");
}
}
public static int recurMatriChain(int i,int j,int p[],int s[][]) {
if(i==j) return 0;
int u= recurMatriChain(i,i,p,s)+ recurMatriChain(i+1,j,p,s)+p[i-1]*p[i]*p[j];
s[i][j]=i;
for(int k=i+1;k<j;k++) {
int t=recurMatriChain(i,k,p,s)+ recurMatriChain(k+1,j,p,s)+p[i-1]*p[k]*p[j];
if(t<u) {
u=t;
s[i][j]=k;
}
}
return u;
}
}
时间复杂度:O(2n)
②动态规划方法
用动态规划解此问题时,在计算过程中,保存已解决的子问题答案,每个子问题只计算一次,而在后面用到时简单地查一下,从而避免了大量的重复计算
public class Matrix_chainMultiplication {
public static void main(String args[]) {
int p[]= {10,100,5,50};
int n=p.length;
int s[][]=new int[n][n];
int m[][]=new int[n][n];
matrixChain(p,s,m,n-1);
System.out.println(m[1][n-1]);
for(int i=1;i<n;i++) {
for(int j=1;j<n;j++) {
System.out.print(s[i][j]+"\t");
}
System.out.println("");
}
}
public static void matrixChain(int p[],int s[][],int m[][],int n) {
for(int i=1;i<=n;i++) m[i][i]=0;
for(int r=2;r<=n;r++) {
for(int i=1;i<=n-r+1;i++) {
int j=i+r-1;
m[i][j]=m[i+1][j]+p[i-1]*p[i]*p[j];
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];
if(t<m[i][j]) {
m[i][j]=t;
s[i][j]=k;
}
}
}
}
}
}
时间复杂度O(n3)
step4:构造最优解
static void traceback(int i,int j,int s[][]) {
if(i==j) return ;
traceback(i,s[i][j],s);
traceback(s[i][j]+1,j,s);
System.out.print("multiple A"+i+",A"+s[i][j]+"\t");
System.out.println("multiple A"+(s[i][j]+1)+",A"+j+"\t");
}
备忘录方法
其思想是记住自然的,但效率低下的递归算法。
与自底向上的方法一样,我们维护一个包含子问题解的表,但填充该表的控制结构更像是递归算法。
每个表条目最初都包含一个特殊值,表示该条目尚未填写。当递归算法展开时第一次遇到子问题时,计算其解,然后将其存储在表中。以后每次遇到这个子问题时,我们只需查找存储在选项卡中的值。
public static int lookupChain(int i,int j,int p[],int s[][],int m[][]) {
if(m[i][j]>0) return m[i][j];
if(i==j) return 0;
int u= lookupChain(i,i,p,s,m)+lookupChain(i+1,j,p,s,m)+p[i-1]*p[i]*p[j];
s[i][j]=i;
for(int k=i+1;k<j;k++) {
int t=lookupChain(i,k,p,s,m)+lookupChain(k+1,j,p,s,m)+p[i-1]*p[k]*p[j];
if(t<u) {
u=t;
s[i][j]=k;
}
}
m[i][j]=u;
return u;
}
0-1背包问题
给定n个物品的重量和价值,将这些物品放在容量C的背包中,以获得背包中的最大总价值。
两个数组v[ ]和w[ ]分别代表价值和重量。
数组x[ ]:当x[i]=0时表示,物品i没有放在包里,当x[i]=1时表示物品i放在包里。
于是问题的最优值就是求最大总容量
最优解就是最大总容量对应的 x[ ]数组
step1:最优解结构
反证法即可
step2:建立最优值的递推公式
m(i,j)表示背包容量为j,可选物品为从i到n的价值的最大值。
初始值:
step3:计算最优值
public class BagProblem {
public static void main(String args[]) {
int w[]= {2,1,3,2};
int v[]= {12,10,20,15};
int c=5;
int n=w.length;
int[][] m=new int[n+1][c+1];
knapsack(v,w,c,n,m);
for(int i=1;i<n+1;i++) {
for(int j=0;j<c+1;j++) {
System.out.print(m[i][j]+"\t");
}
System.out.println(" ");
}
System.out.print(m[1][c]);
}
public static void knapsack(int[] v,int[] w,int c,int n,int[][] m) {
//赋初值
for(int j=w[n-1];j<m[0].length;j++) {
m[n][j]=v[n-1];
}
for(int i=n-1;i>=1;i--) {
for(int j=0;j<w[i-1];j++) {
m[i][j]=m[i+1][j];
}
for(int j=w[i-1];j<m[0].length;j++) {
m[i][j]=Integer.max(m[i+1][j],m[i+1][j-w[i-1]]+v[i-1]);
}
}
}
}
step4:构造最优解
public void trackback(int[][] m,int w[] ,int c,int n ,int[] x ) {
for(int i=1;i<n;i++) {
if(m[i][c]==m[i+1][c]){ x[i]=0;}
else {
x[i]=1;
c=c-w[i-1];
}
}
x[n]=(m[n][c]==0)?0:1;
}
最优二叉搜索树
这里二叉搜索树的叶子是null;
二叉树中的叶顶点是形如(xi, xi+1) 的开区间。
利用动态规划构造对标识符集合
{a1, a2, …, an}的最优二叉搜索树算法包括成功检索和不成功检索)。
step1:最优子结构
](https://img-blog.csdnimg.cn/20200208140317632.png)
设COST(L) 和COST® 分别是二分检索树T的左子树和右子树的成本
则检索树T的成本是:P(k)+ COST(L) + COST®
若 T 是最优的,则上式及 COST(L) 和COST® 必定都取最小值。
step2:建立最优值的递推公式
step3:计算最优值
public class OptimalBinarySearchTree {
public static void main(String args[]) {
double p[]= {0,0.5,0.1,0.05};
double q[]= {0.15,0.1,0.05,0.05};
int n=q.length;//n=4
double m[][]=new double[n+1][n];
int[][] s=new int[n+1][n];
double w[][]=new double[n+1][n];
optimalBinarySearchTree(p,q,n,m,s,w);
for(int i=1;i<n+1;i++) {
for(int j=0;j<n;j++) {
System.out.print(w[i][j]+"\t");
}
System.out.println("");
}
}
public static void optimalBinarySearchTree(double[] p, double[] q,int n, double[][] m, int[][] s, double[][] w) {
//初始化对角线
for(int i=0;i<n;i++) {
w[i+1][i]=q[i];
m[i+1][i]=0;
}//构造没有内部节点的情况
for(int r=0;r<n;r++) {
for(int i=1;i<n-r;i++) {
int j=i+r;
w[i][j]=w[i][j-1]+p[j]+q[j];
m[i][j]=m[i+1][j];
s[i][j]=i;
for(int k=i+1;k<=j;k++) {
double t=m[i][k-1]+m[k+1][j];
if(t<m[i][j]) {
m[i][j]=t;
s[i][j]=k;
}
}
m[i][j]+=w[i][j];
}
}
}
}
step4:构造最优解
复杂度