动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题.但是经分解得到的子问题往往不是互相独立的。
不同子问题的数目常常只有多项式量级。在用分治法求解时,有些子问题被重复计算了许多次。
如果能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,就可以避免大量重复计算,从而得到多项式时间算法。
基本思想: 将待求解的问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。
基本要素: 最优子结构性质和重叠子问题性质
与分治法的区别:
分治算法是把原问题分解为若干个子问题,自顶向下求解子问题,合并子问题的解,从而得到原问题的解。动态规划也是把原始问题分解为若干个子问题,然后自底向上,先求解最小的子问题,把结果存在表格中,在求解大的子问题时,直接从表格中查询小的子问题的解,避免重复计算,从而提高算法效率。
问题:给定n个矩阵{A1,A2,…,An},其中,Ai与Ai+1是可乘的,(i=1,2 ,…,n-1)。用加括号的方法表示矩阵连乘的次序,不同的计算次序计算量(乘法次数)是不同的,找出一种加括号的方法,使得矩阵连乘的次数最小
解决思路:
A[i:j] 表示矩阵 i 到 j 的连乘
假设在第 k 位置上找到最优解,则问题变成了两个子问题:A[i:k] , A[k+1,j]
用 m[i][j] 表示矩阵连乘的最优值,那么两个子问题对应的最优值变成 m[i][k], m[k+1][j],且原问题的最优值为 m[1][n]。
伪代码
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;i++){
int j=i+r-1;
m[i][j] = m[i+1][j] + p[i-1]*p[i]p[j];
for(int k=i+1;k<j;k++){
int t = m[i][k]+m[k+1][j] + p[i-1]*p[i]*p[j];
if(t<m[i][j]){
m[i][j] = t;
s[i][j] = k;
}
}
}
}
}
问题:字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列。令给定的字符序列X=“x0,x1,…,xm-1”,序列Y=“y0,y1,…,yk-1”是X的子序列,存在X的一个严格递增下标序列
解决思路:
对于 X={x1,x2,x3,…,xm},Y = {y1,y2,y3,…,yn}
由上面的分析可以得到如下递归关系,c[i][j] 表示 Xi 和 Yi 的最长公共子序列长度
结果如下:
伪代码:
public static int lcsLength(char[] x,char[] y,int[][] b){//b用来记录路径
int m=x.length-1'
int n = y.length-1;
int[][] c = new int[m+1][n+1];
for(int i=1;i<=m;i++){
c[i][0]=0;
}
for(int i=1;i<=n;i++){
c[0][i]=0;
}
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
if(x[i]==y[j]){
c[i][j] = c[i-1][j-1]+1;
b[i][j] = 1;
}else if(c[i-1][j]>=c[i][j-1]){
c[i][j] = c[i-1][j];
b[i][j] = 2;
}else{
c[i][j] = c[i][j-1];
b[i][j] = 3;
}
}
return c[m][n];
}
}
问题:给定凸多边形P,以及定义在由多边形的边和弦组成的三角形上的权函数w。要求确定该凸多边形的三角剖分,使得该三角剖分中诸三角形上权之和为最小。
解决思路:
和矩阵连乘问题是类似的,若凸(n+1)边形 P = {v0,v1,v2,…,vn} 的最优三角剖分 T 包含三角形 v0vkvn(1<=k<=n-1),则 T 的权分为三个部分权的和:三角形v0vkvn的权、子多边形{v0,v1,…,vk} 和{vk+1,…,vn}。设 t[i][j] 表示凸多边形{vi-1,vi,…,vj}的最优三角剖分所对应的权和,则原问题的最优权值为t[1][n]。
伪代码
和上面的矩阵连乘一样,这里就不写了。
问题:确定导线集Nets = {i,π(i),1 ≤ i ≤ n}的最大不相交子集
解决思路:
记N(i,j) = {t|(t, π(t)) ∈ Nets,t ≤ i, π(t) ≤ j }. N(i,j)的最大不相交子集为MNS(i,j)Size(i,j)=|MNS(i,j)|,即最大不相交子集的个数。
public static void mnset(int[] c,int[][] size){
int n=c.length-1;
for(int j=0;j<c[1];j++){
size[1][j] = 0;
}
for(int j=c[1];j<=n;j++){
size[1][j] = 1;
}
for(int i=2;i<n;i++){
for(int j=0;j<c[i];j++){
size[][] = size[i-1][j];
}
for(int j=c[i];j<=n;j++){
size[][] = Math.max(size[i-1][j],size[i-1][c[i]-1]+1);
}
}
size[n][m] = Math.max(size[n-1][n],size[n-1][c[n]-1]+1);
}
问题:n个作业 N={1,2,…,n}要在2台机器M1和M2组成的流水线上完成加工。每个作业须先在M1上加工,然后在M2上加工。M1和M2加工作业 i 所需的时间分别为 ai 和bi,每台机器同一时间最多只能执行一个作业。
流水作业调度问题要求确定这n个作业的最优加工顺序,使得所有作业在两台机器上都加工完成所需最少时间
解决思路:
由上面的递归式就可以设计出我们的动态规划方法,但是还可以使用 johnson 法则进行简化:
代码:
public static int flowShop(int a[],int b[],int c[]){//c为最优调度顺序
int n=a.length-1;
Element d[]=new Element[n];
for(int i=0;i<n;i++){
int key=a[i]>b[i]? b[i]:a[i];//找作业在两台机器上处理时间最小的那个作业处理时间
boolean job = a[i]<=b[i];
d[i]=new Element(key,i,job);
}
Arrays.sort(d);//将所有作业的key进行从小到大排序
int j=0,int k = n-1;
//将作业按照Johnson法则排序放入c中
for(int i=0;i<n;i++){
if(d[i].job) c[j++]=d[i].index;//如果ai<=bi,将其作业序号放入c数组中(从头开始放)
else c[k--]=d[i].index;//否则
}
//真正的动态规划部分!
j=a[c[0]];//第一个作业在M1上的处理时间
k=j+b[c[0]];//第一个作业处理完所需时间
for(int i=1;i<n;i++){
j+=a[c[i]];//第i个作业在机器上加工完成所需时间
k=j<k? k+b[c[i]]:j+b[c[i]];
/*如果此作业在M1上加工完成时间
(包含前面作业在M1上的所用时间和)小于上一个作业全部完成时间(还得等M2做完),则此作业所需时间
为k+b[c[i]],否则为j+b[c[i]]*/
}
return k;
}
public static class Element{
public int key;
public int index;
public boolean job;
private Element(int kk,int ii,boolean jj){
key=kk;
index=ii;
job=jj;
}
}
问题:给定 n 种物品和一背包,物品 i 的重量是 wi,其价值为 vi,背包的容量为 C,问应该如何选择装入背包的物品,使得装入背包中物品的总价值最大?
解决思路:
代码: 待补充