问题引入:任何的数学公式都可以被转换为递归算法,但是这样会往往导致程序的低效。将递归算法。将递归算法重新写成非递归算法,让后者把那些子问题的答案系统地记录在一个表内,利用这种方法的一种技巧叫作动态规划
用一个表代替递归:计算斐波那契数列
public static int fib (int n){
if (n<=1 ) {
return 1 ;
}else {
return fib(n-1 )+fib(n-2 );
}
}
public static int fibonacci (int n){
if (n<=0 ) {
return 1 ;
}
int last=1 ;
int nextlast=1 ;
int answer=1 ;
for (int i = 2 ; i <=n; i++) {
answer=last+nextlast;
nextlast=last;
last=answer;
}
return answer;
}
递归算法如此慢的原因在于算法模拟了递推。为了计算F(N),需存在一个对Fn-1和Fn-2的调用。然而,由于Fn-1递归地对Fn-2和Fn-3进行调用,因此存在两个单独计算Fn-2的调用。我们可以发现,Fn-3就被计算了3次,Fn-4计算了5 次,Fn-5计算了8次等等。冗余计算的增长是爆炸性。如果递归模拟递归算法能够保留一个表面对已经解决过的
算法的基本思想:动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,适用动态规划的问题经分解得到的子问题往往不相互独立的。若用分治法来解这类问题,那么分解得到的子问题数目很多,而且有些子问题会被重复计算许多次。如果能够保存已解决的子问题的答案,而在需要时在找出已求得的答案,就可以避免大量重复的计算。
基本要素:1.最优化原理:一个最优化策略具有这样的性质,不论过去状态和决策如何,对前面的决策所形成的状态而言,余下的诸决策必须构成最优策略。简而言之,一个最优化策略的子策略总是最优的。一个问题满足最优化原理又称其具有最优子结构性质。所用的方法具有普遍性:首先假设由问题的最优解导出的子问题的解不是最优的,然后再设法说明在这个假设下可构造出原问题最优解更好的解,从而导致矛盾。这是问题能用动态规划的前提。
2.备忘录方法:备忘录方法的控制结构与直接递归方法的控制结构相同,区别在于备忘录方法为每个解过的子问题建立了备忘录以备需要时查看避免了相同子问题的重复问题
3.子问题的重叠性:动态规划将原来具有指数级时间复杂度的搜索算法改进成了具有多项式时间复杂度的算法。其中的关键在于解决冗余,这是动态规划算法根本目的。动态规划算法,对每一个子问题只解决一次,而后将其解保存在一个表格中,当再次需要解决子问题时,只是简单地用常数时间查看一下结果。
例题:0-1背包问题:给定n种物品和一背包。物品i的重量是wi,其价值为vi,背包的容量为C。问:应如何选择装入背包的物品,使得装入背包中物品的总价值最大
思路:利用动态规划思想,子问题为:f[i][v]表示前i件物品掐放入一个容量为v的背包中可以或得最大价值。状态转移方程为法f[i][v]=max{f[i-1][v],f[i-1][v-w[i]]+p[i]}。如果只考虑第i件物品放或者不放,那么就可以在转化为前i-1件物品的问题,1、如果不放第i件物品,则问题转化为 前i-1件物品放入容量为v的背包中;2、如果放第i件物品,则问题转化为 前i-1件物品放入剩下的容量为v-c[i]的背包中(此时能获得的最大价值就是f [i-1][v-c[i]]再加上通过放入第i件物品获得的价值w[i])。则f[i][v]的值就是1、2中最大的那个值。
public class Package0_1 {
public static void main (String[] args) {
int c=8 ;
int N=4 ;
int v[]={0 ,2 ,1 ,4 ,3 },w[]={0 ,1 ,4 ,2 ,3 };
int x[]=new int [N+1 ];
int m[][]=new int [10 ][10 ];
System.out .println("物品的重量:" );
for (int i = 0 ; i < w.length; i++) {
System.out .print(w[i]+" " );
}
System.out .println();
System.out .println("物品的价值" );
for (int i = 0 ; i < v.length; i++) {
System.out .print(v[i]+" " );
}
System.out .println();
System.out .println("背包能包装的最大价值:" );
function(v, w, c, N, m);
System.out .println();
System.out .println("背包装下的物品编号为:" );
putin(m, w, c, N, x);
}
public static void function (int v[],int w[],int c,int n,int m[][]){
int jM=Math.min(w[n]-1 , c);
for (int i = 0 ; i <=jM; i++) {
m[n][i]=0 ;
}
for (int i = w[n]; i <=c; i++) {
m[n][i]=v[n];
}
for (int i = n-1 ; i >1 ; i--) {
jM=Math.min(w[i]-1 , c);
for (int j = 0 ; j <=jM; j++) {
m[i][j]=m[i+1 ][j];
}
for (int j = w[i]; j <=c; j++) {
m[i][j]=Math.max(m[i+1 ][j],m[i+1 ][j-w[i]]+v[i]);
}
}
m[1 ][c]=m[2 ][c];
if (c>=w[1 ]) {
m[1 ][c]=Math.max(m[1 ][c], m[2 ][c-w[1 ]]+v[1 ]);
}
System.out .print(m[1 ][c]);
}
public static void putin (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-=w[i];
}
}
x[n]=(m[n][c]!=0 )?1 :0 ;
for (int i = 0 ; i < x.length; i++) {
if (x[i]==1 ) {
System.out .print(i+" " );
}
}
}
}