动态规划
动态规划在查找有很多重叠子问题的情况的最优解时有效。它将问题重新组合成子问题。为了避免多次解决这些子问题,它们的结果都逐渐被计算并被保存,从简单的问题直到整个问题都被解决。因此,动态规划保存递归时的结果,因而不会在解决同样的问题时花费时间。
1. 斐波纳契数
- 非递归实现
#include <stdio.h> /* 利用 迭代 计算 斐波那契数 其形式为: 1, 1, 2, 3, 5, 8, 13, 21, 34 … */ long fibonacci(int n){ long result; long previous; long next; next = previous = 1; if(n<=2){ return 1; } int i =3; /* 用于计算已经计算到第几个 */ while( i<=n ){ result = previous + next; previous = next; next = result; i++; } return result; }
- 递归实现
int Fibonacci(int i) { if(i<1) return 0; if(i==1) return 1; return Fibonacci(i-1)+Fibonacci(i-2); }
但是这个递归实现是一个指数时间的算法,因为例如:在计算fibonacci(10) 时,fibonacci(3)被计算了21次;但在计算fibonacci(30) 时,fibonacci(3)被计算了 317811 次;这 317811 次计算结果完全一样,除了一次有用外,其余都是浪费。
那我们怎样改进呢?这就用到我们今天所要用到的动态规划。程序改进如下:
//斐波纳契数(动态规划的递归实现) //主要思想是:通过把递归产生的值保存到一个数组中,只要数组中存在就直接返回,避免重新计算 int Fibonacci(int i,long knownF[]) { int t; if(knownF[i]!=0) return knownF[i]; if(i==0) t=0; if(i==1) t=1; if(i>1) t=Fibonacci(i-1,knownF)+Fibonacci(i-2,knownF); knownF[i]=t; return knownF[i]; }
改进前程序复杂度为O(φN)(φ为黄金分割率1.618...),但是动态递归之后就变为线性的了。
2. 背包问题
- 背包问题的递归实现
//背包问题(递归实现)--非0-1背包 typedef struct { int size; int val} Item; //物品结构:包括大小和价值 int knap(int cap) //cap容量 N种物品 { int i,space,max,t; for (i=0,max=0; i < N; ++i) { if((space=cap-items[i].size)>=0) if((t=knap(space)+items[i].val)>=max) max=t; } return max; }
- 背包问题的动态规划递归实现
//M容量,maxKnow[i]保存容量为i大小的max //Item items[N]={{3,4,'a'},{4,5,'b'},{7,10,'c'},{8,11,'d'},{9,13,'e'}}; //Item itemKnown[M+1]; //保存选出的最优解的物品 //int maxKnowm[M+1]; int knap(int M) { int i,space,max,maxi=0,t; if(maxKnowm[M]!=0) return maxKnowm[M]; for (i=0,max=0; i < N; ++i) { if((space=M-items[i].size)>=0) if((t=knap(space)+items[i].val)>=max) { max=t; maxi=i; } } maxKnowm[M]=max; itemKnow[M]=Items[maxi]; return max; }
我们保存了最优解的索引,因此可以把背包的内容输出。
itemKnow[M]是背包为M大小的时候,最优背包中的一个物品。这个物品的大小为itemKnow[M].size。如果把这个物品从最优解中去掉,就成了求解M-itemKnow[M].size大小的背包最优解的问题。同理,按照程序,itemKnow[M-itemKnow[M].size]是背包为M-itemKnow[M].size大小的时候,最优解中的一个物品。这样不断递归就能输出最优背包中的物品了。
- 0-1背包的动态规划问题
本程序转自:http://wenku.baidu.com/view/5d1aa737eefdc8d376ee3247.html###
0/1背包问题求解
/************************************************************************ * 0/1背包问题求解 (visual studio 2005) * 给定一个载重量为m,及n个物品,其重量为wi,价值为vi,1<=i<=n * 要求:把物品装入背包,并使包内物品价值最大 ************************************************************************/ #include <stdio.h> #include <stdlib.h> #include <string> using namespace std; #define FILENAMELENGTH 100 class CBeibao { public: int m_nNumber; //物品数量 int m_nMaxWeight; //最大载重量 int *m_pWeight; //每个物品的重量 int *m_pValue; //每个物品的价值 int *m_pCount; //每个物品被选中的次数 int m_nMaxValue; //最大价值 public: CBeibao(const char *filename); ~CBeibao(); int GetMaxValue(); int GetMaxValue(int n,int m,int *w,int *v,int *c); void Display(int nMaxValue); void Display(int nMaxValue,const char *filename); }; //读入数据 CBeibao::CBeibao(const char *filename) { FILE *fp=fopen(filename,"r"); if(fp==NULL) { printf("can not open file!"); return; //exit(0); } //读入物品数量和最大载重量 fscanf(fp,"%d%d",&m_nNumber,&m_nMaxWeight); m_pWeight=new int[m_nNumber+1]; m_pValue=new int[m_nNumber+1]; m_pWeight[0]=0; //读入每个物品的重量 for(int i=1;i<=m_nNumber;i++) fscanf(fp,"%d",m_pWeight+i); m_pValue[0]=0; //读入每个物品的价值 for(int i=1;i<=m_nNumber;i++) fscanf(fp,"%d",m_pValue+i); //初始化每个物品被选中次数为0 m_pCount=new int[m_nNumber+1]; for(int i=0;i<=m_nNumber;i++) m_pCount[i]=0; fclose(fp); } CBeibao::~CBeibao() { delete[] m_pWeight; m_pWeight=NULL; delete[] m_pValue; m_pValue=NULL; delete[] m_pCount; m_pCount=NULL; } /************************************************************************ * 动态规划求出满足最大载重量的最大价值 * 参数说明:n:物品个数 * m:背包载重量 * w:重量数组 * v:价值数组 * c:是否被选中数组 * 返回值:最大价值 ************************************************************************/ int CBeibao::GetMaxValue(int n,int m,int *w,int *v,int *c) { int row=n+1; int col=m+1; int i,j; //循环变量:前i个物品能够装入载重量为j的背包中 //value[i][j]表示前i个物品能装入载重量为j的背包中物品的最大价值 int **value=new int*[row]; for(i=0;i<row;i++) value[i]=new int[col]; //初始化第0行 for(j=0;j<col;j++) value[0][j]=0; //初始化第0列 for(i=0;i<row;i++) value[i][0]=0; //计算 for(i=1;i<row;i++) { for(j=1;j<col;j++) { //w[i]>j,第i个物品不装入背包 value[i][j]=value[i-1][j]; //w[i]<=j,且第i个物品装入背包后的价值>value[i-1][j],则记录当前最大价值 if (w[i]<=j) { int temp=value[i-1][j-w[i]]+v[i]; if(w[i]<=j && temp>value[i][j]) value[i][j]=temp; } } } //逆推求装入的物品 j=m; for(i=row-1;i>0;i--) { if(value[i][j]>value[i-1][j]) { c[i]=1; j-=w[i]; } } //记录最大价值 int nMaxVlaue=value[row-1][col-1]; //释放该二维数组 for(i=0;i<row;i++) { delete [col]value[i]; value[i]=NULL; } delete[] value; value=NULL; return nMaxVlaue; } int CBeibao::GetMaxValue() { int nValue=GetMaxValue(m_nNumber,m_nMaxWeight,m_pWeight,m_pValue,m_pCount); m_nMaxValue=nValue; return nValue; } //显示结果 void CBeibao::Display(int nMaxValue) { printf(" %d \n",nMaxValue); for(int i=1;i<=m_nNumber;i++) { if(m_pCount[i]) printf(" %d %d \n",i,m_pCount[i]); } printf(" "); } void CBeibao::Display(int nMaxValue,const char *filename) { FILE *fp=fopen(filename,"w"); if(fp==NULL) { printf("can not write file!"); return; //exit(0); } fprintf(fp,"%d \n",nMaxValue); for(int i=1;i<=m_nNumber;i++) { if(m_pCount[i]) fprintf(fp,"%d %d\n",i,m_pCount[i]); } fclose(fp); } //显示菜单 void show_menu() { printf("--------------------------------------------- \n"); printf("input command to test the program \n"); printf(" i or I : input filename to test \n"); printf(" q or Q : quit \n"); printf("--------------------------------------------- \n"); printf("$ input command >"); } void main() { char sinput[10]; char sfilename[FILENAMELENGTH]; show_menu(); scanf("%s",sinput); while(stricmp(sinput,"q")!=0) { if(stricmp(sinput,"i")==0) { printf(" please input a filename:"); scanf("%s",sfilename); //获取满足最大载重量的最大价值 CBeibao beibao(sfilename); int nMaxValue=beibao.GetMaxValue(); if(nMaxValue) { beibao.Display(nMaxValue); int nlen=strlen(sfilename); strcpy(sfilename+nlen-4,"_result.txt"); beibao.Display(nMaxValue,sfilename); } else printf(" error! please check the input data! "); } //输入命令 printf("\n$ input command >"); scanf("%s",sinput); } }