1201动态规划.docx
《1201动态规划.docx》由会员分享,可在线阅读,更多相关《1201动态规划.docx(37页珍藏版)》请在冰豆网上搜索。
1201动态规划
第3章动态规划
如果问题是由重叠的子问题所构成的,就可以用动态规划技术来解决。
一般来说,这样的子问题出现在对给定问题求解的递推关系中,这个递推关系包含了相同类型的更小子问题的解。
动态规划建议,与其对重叠的子问题一次又一次地求解,还不如对每个较小的子问题只求一次并把结果记录在表中,这样就可以从表中得到原始问题的解。
3.1Fibonacci数列问题
求Fibonacci数列的第n项。
f(n)=f(n-1)+f(n-2)
f
(1)=1,f
(2)=1
(1)直接递归法,此法会大量重复计算子问题的结果
intFib(intn){
if(n==1||n==2)return1;
returnFib(n-1)+Fib(n-2);
}
(2)动态规划方法,使用数组存储每个子问题的结果
intf[100];
intFib(intn){
f[1]=f[2]=1;
for(inti=3;i<=n;i++)
f[i]=f[i-1]+f[i-2];
returnf[n];
}
(3)改进,只存储最后2个元素的值,避免使用数组
intFib(intn){
intf,f1,f2;
f1=f2=1;
for(inti=3;i<=n;i++){
f=f1+f2;
f1=f2;
f2=f;
}
returnf;
}
(4)备忘录方法,使用递归方法,但利用数组存储来避免重复计算子问题
intf[100];
intFib(intn){
if(f[n]>0)returnf[n];
if(n==1||n==2)return1;
f[n]=Fib(n-1)+Fib(n-2);
returnf[n];
}
3.2计算二项式系数
二项式公式为
(a+b)n=C(n,0)an+…+C(n,k)an-kbk+…+C(n,n)bn
其中C(n,k)为二项式系数
(a+b)n-1=C(n-1,0)an-1+…+C(n-1,k-1)an-kbk-1+C(n-1,k)an-k-1bk+…+C(n-1,n-1)bn-1
由(a+b)n=(a+b)(a+b)n-1可得:
(1)计算二项式系数的递归函数为
intC(intn,intk){
if(k==0||n==k)return1;
returnC(n-1,k-1)+C(n-1,k);
}
但用这种递归方法效率非常低(计算C(32,20)都用了好几秒钟)。
(2)用动态规划来求二项式系数
intm[100][100];
intC(intn,intk){
for(inti=0;i<=n;i++)
m[i][0]=m[i][i]=1;
for(inti=1;i<=n;i++)
for(intj=1;j
m[i][j]=m[i-1][j-1]+m[i][j-1];
returnm[n][k];
}
(3)用备忘录方法来求二项式系数
intm[100][100]={0};
intC(intn,intk){
if(m[n][k]==0)
if(k==0||n==k)m[n][k]=1;
elsem[n][k]=C(n-1,k-1)+C(n-1,k);
returnm[n][k];
}
3.3整数划分问题
1、把一个正整数n分成任意个正整数的和,有多少种分法?
例如正整数6有如下11种不同的划分:
6;
5+1;
4+2,4+1+1;
3+3,3+2+1,3+1+1+1;
2+2+2,2+2+1+1,2+1+1+1+1;
1+1+1+1+1+1。
设f(n,m)表示整数n的划分中,每个数不大于m的划分数。
则划分数可以分为两种情况:
a.划分中每个数都小于m。
划分数为f(n,m-1)
b.划分中至少有一个数为m。
就相当于把n-m进行划分,故划分数为f(n-m,m)
于是:
f(n,m)=f(n,m-1) +f(n-m,m)
其它条件为:
f(0,m)=1;f(1,m)=1;f(n,1)=1;
程序为
intf(intn,intm){
if(m==1||n==1||n==0)return1;
if(nreturnf(n,m-1)+f(n-m,m);
}
动态规划方法
intmain(){
inta[11][11],n;
for(inti=1;i<=10;i++)
a[i][1]=a[1][i]=a[0][i]=1;
for(inti=2;i<=10;i++){
for(intj=2;j<=i;j++)
a[i][j]=a[i][j-1]+a[i-j][j];
for(intj=i+1;j<=10;j++)
a[i][j]=a[i][i];
}
cin>>n;
cout<}
2、把一个正整数n分成m个正整数的和,有多少种分法?
设f(n,m)把n分成m个正整数的和的分法。
则划分数可以分为两种情况:
a.划分中每个数都>=2。
相当于先拿出m个1,将剩下的再分,划分数为f(n-m,m)
b.划分中至少有一个数为1。
相当于把n-1分成m-1个数,故划分数为f(n-1,m-1)
于是:
f(n,m)=f(n-1,m-1)+f(n-m,m)
其它条件为:
f(n,1)=1;当n程序为
intf(intn,intm){
if(m==1)return1;
if(nreturnf(n-1,m-1)+f(n-m,m);
}
3、把一个正整数n分成不超过m个正整数的和,有多少种分法?
设f(n,m)表示最多用m个整数来划分n,可分为两种情况:
a.把n分成不超过m-1个正整数的和。
划分数为f(n,m-1)
b.把n分成正好m个正整数的和。
就相当于先拿出m个1,再把n-m分成不超过m个正整数的和,故划分数为f(n-m,m)
于是:
f(n,m)=f(n,m-1) +f(n-m,m)
此题居然和“1、用不大于m的数去划分n”完全等价。
4、“2、把正整数n分成m个正整数的和”可以转换为“把正整数n分成不超过m个正整数的和”,只需先拿出m个1,然后再将n-m分成不超过m个正整数的和。
即
f2(n,m)=f3(n-m,m)
3.40-1背包问题
0-1背包问题:
给定n种物品和一个背包,物品i的重量为wi,其价值为vi,背包的承重量为c。
问如何选择物品装入背包,使得物品的总价值最大。
物品的重量为:
w={w1,w2,…,wi,…,wn},价值为:
v={v1,v2,…,vi,…,vn},背包容量为c。
设f(i,j)表示剩余容量为j,剩余物品为i,i+1,…,n时的最优解的值,则有
例:
设n=5,c=10,w={2,2,6,5,4},v={6,3,5,4,6},问题求解过程示意图如下:
例1:
0/1背包问题的递归解法
#include
usingnamespacestd;
intn=5,w[]={0,2,2,6,5,4},v[]={0,6,3,5,4,6};
intf(inti,intj){
if(i==n)returnj0:
v[n];
if(jreturnmax(f(i+1,j),f(i+1,j-w[i])+v[i]);
}
例2:
用动态规划解决0/1背包问题
设n=5,c=10,w={2,2,6,5,4},v={6,3,5,4,6}
w
v
0
1
2
3
4
5
6
7
8
9
10
1
2
6
15
2
2
3
0
0
3
3
6
6
9
9
9
10
11
3
6
5
0
0
0
0
6
6
6
6
6
10
11
4
5
4
0
0
0
0
6
6
6
6
6
10
10
5
4
6
0
0
0
0
6
6
6
6
6
6
6
#include
usingnamespacestd;
floatm[100][100];
voidKnapsack(floatv[],intw[],intc,intn){
inti,j,jmax;
jmax=min(w[n]-1,c);
for(j=0;j<=jmax;j++)m[n][j]=0;
for(j=w[n];j<=c;j++)m[n][j]=v[n];
for(i=n-1;i>1;i--){
jmax=min(w[i]-1,c);
for(j=0;j<=jmax;j++)m[i][j]=m[i+1][j];
for(j=w[i];j<=c;j++)
m[i][j]=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]=max(m[1][c],m[2][c-w[1]]+v[1]);
}
voidtraceback(intw[],intc,intn,intx[]){
for(inti=1;iif(m[i][c]==m[i+1][c]){
x[i]=0;
}else{
x[i]=1;
c-=w[i];
}
x[n]=m[n][c]?
1:
0;
}
intmain(){
floatv[]={0,6,3,5,4,6};
intc=10,n=5,w[]={0,2,2,6,5,4},x[6];
Knapsack(v,w,c,n);
traceback(w,c,n,x);
cout<<"themaxvalueis:
"<for(inti=1;i<=n;i++)
cout<}
例3:
0/1背包问题的备忘录解法
#include
usingnamespacestd;
floatm[100][100];
intn=5,w