演算法篇-5-動態規劃-01揹包&流水作業排程&&整數線性規劃&樹的最大連通分支
0-1揹包
給定N中物品和一個揹包。物品i的重量是Wi,其價值位Vi ,揹包的容量為C。問應該如何選擇裝入揹包的物品,使得轉入揹包的物品的總價值為最大?
且對於物品i只有裝一次或者一次不裝兩種選擇。
令m(i,j)表示後i到n (1<=i<=n)個物品中能夠裝入容量為j(1<=j<=C)的揹包中的物品的最大價值,則可以得到如下的動態規劃函式:
m(n,j) = vn j>=wn;
(2) m(n,j) = 0 0<=j<wn;
(3) m(i,j)=m(i+1,j) 0<=j<wi
(4) m(i,j)=max{m(i+1,j) ,m(i+1,j-wi)+vi)} j>wi
(3) 式表明:如果第i個物品的重量大於揹包的容量,則裝入i物品得到的最大價值和裝入i+1物品得到的最大價是相同的,即物品i不能裝入揹包;第(4)個式子表明:如果第i個物品的重量小於揹包的容量,則會有一下兩種情況:(a)如果把第i個物品裝入揹包,則揹包物品的價值等於第i+1個物品裝入容量位j-wi 的揹包中的價值加上第i個物品的價值vi; (b)如果第i個物品沒有裝入揹包,則揹包中物品價值就等於把i+1後物品裝入容量為j的揹包中所取得的價值。顯然,取二者中價值最大的作為把後i個物品裝入容量為j的揹包中的最優解。
(1)(2) 確定了邊界。
以上是為了配合程式碼從後向前分析,其實我們完全可以從前向後分析,
那麼:
m(1,j) = 0; 0<=j<wi;
m(1,j) = v1; j>wi;
m(i,j) = m(i-1,j); 0<=j<wi;
m(I,j) = max{ m(i-1,j) , m(i-1,j-wi)+vi}; j>wi;
Code:
template<class T_> void Knapsack(T_ v[],intw[],int c,int n,T_ m[]) { //v[]/w[] 每物品價值 /重量 //c揹包容量,n物品個數,m最優值結果 //m[i][j] 表示揹包容量為j ,可選物品為 {i,i+1,...,n}時的最優解 int jMax = w[n]-1>c?c:w[n]-1; for(int j=0; j<=jMax; j++) m[n*(c+1)+j]=0; for(int j=w[n]; j<=c; j++) m[n*(c+1)+j] = v[n]; for(int i=n-1; i>1; i--) { //計算第i物品,放入揹包時的最優值。 jMax =w[i]-1>c?c:w[i]-1; for(int j=0;j<=jMax; j++) m[i*(c+1)+j] = m[(i+1)*(c+1)+j]; for(int j=w[i];j<=c; j++) { T_ bb = (m[(i+1)*(c+1)+(j-w[i])]+v[i]); m[i*(c+1)+j] = m[(i+1)*(c+1)+j]>bb?m[(i+1)*(c+1)+j]:bb; } //只計算i=1時最後一個,節省一點計算 m[c+1+c]=m[2*(c+1)+c]; if(c>=w[1]) { T_ cc = m[2*(c+1)+(c-w[1])]+v[1]; m[c+1+c] = m[c+1+c]>cc?m[c+1+c]:cc; } } } template<class T_> void Traceback(T_m[],int w[],int c,int n,int x[]) { //m[i][j] 表示揹包容量為j ,可選物品為 {i,i+1,...,n}時的最優解 //w[] 每物品價值 /重量 //c揹包容量,n物品個數 //x[]選擇結果序列 int current_c=c; for(int i=1; i<n; ++i) { if(m[i*(c+1)+current_c]== m[(i+1)*(c+1)+current_c]) x[i] = 0; else { x[i]=1; current_c-= w[i]; } } x[n]=(m[n*(c+1)+current_c])?1:0; } int main() { int n=5; int c= 10; int w[]={0,2,2,6,5,4}; int v[]={0,6,3,5,4,6}; int m[(n+1)*(c+1)]; Knapsack(v,w,c,n,m); cout<<"最優值"<<m[c+1+c]<<endl; int x[5+1]; Traceback(m,w,c,n,x); cout<<"Select: "; for(int i=1; i<=n; ++i) { cout<<x[i]<<''; } cout<<endl; cin.get(); return 0; }
流水作業排程
n個作業{1,2,…,n}要在由2臺機器M1和M2組成的流水線上完成加工。每個作業加工的順序都是先在M1上加工,然後在M2上加工。M1和M2加工作業i所需的時間分別為ai和bi。流水作業排程問題要求確定這n個作業的最優加工順序,使得從第一個作業在機器M1上開始加工,到最後一個作業在機器M2上加工完成所需的時間最少。
整數線性規劃
設m(i,j) 為選擇前I 項,總資源為j時的最優回報值。
Wi為投資i單位所需資源
Vi 為投資I 後單位所獲回報
Xi 為投資I 多少單位
那麼類似01揹包可得
M(1,j) = 0 , j<wi;
M(1,j)=k* v1 ,k*w1 <=j<(k+1)w1;
M(i,j) = m(i-1,j) , j<wi;
M(i,j) = max{ m(i-1,j) ,max{m(i-1,j-x*wi)+x*vi}(1<=x<=k)} , k*wi<=j<(k+1)wi;
Code:
void how_to_invest(intn,int all,int w[],int v[])
{
//n個選項 all=所有資源
//x[i]/w[i]/v[i] 第i個專案投資了多少單位/單位需求資源數/單位回報數
//m[i][j]為選擇前i項,總資源為j時的最優回報值。
//X[i][j]該最由下投資第i物品多少單位
for(int i=1; i<=n; i++) m[i][0]=0;
for(int i=1; i<w[1]; i++) m[1][i]=0;
for(int i=w[1]; i<=all; i++) {
m[1][i] =int(i/w[1])*v[1];
X[1][i] =int(i/w[1]);
}
//for(int i=1; i<=all; i++)cout<<m[1][i]<<endl;
for(int i=2; i<=n; i++) {
//從前向後
int Jmax =all<(w[i]-1)?all:w[i]-1;
for(int j=1;j<=Jmax; j++) { //j<w[i];
m[i][j] = m[i-1][j];
X[i][j]=0;
}
for(int j=w[i];j<=all; j++) { //j>=w[i];
//max{m(i-1,j-x*wi)+x*vi}(1<=x<=k)}
int Mmax = 0;
int k = int(j/w[i]);
for(int x=w[i]; x<=k; x++) {
if(Mmax<m[i-1][j-x*w[i]]+x*v[i]){
X[i][j]=x;
Mmax = m[i-1][j-x*w[i]]+x*v[i];
}
}
//max{ m(i-1,j) ,max{m(i-1,j-x*wi)+x*vi}(1<=x<=k)}
if(m[i-1][j]>Mmax) {
m[i][j]=m[i-1][j];
X[i][j]=0;
} else {
m[i][j]= Mmax;
}
}
}
}
void TraceBack(intw[],int n,int all) {
//n個選項 all=所有資源
if(X[n][all]==-1) return ;
if(X[n][all]!=0) {
cout<<"第"<<n<<"投資資源數"<<X[n][all]*w[n]<<endl;
}
TraceBack(w,n-1,all-X[n][all]*w[n]);
}
int main(int argc,char*argv[])
{
memset(X,-1,sizeof(int)*(my_n+1)*(my_all+1));
int w[] = {0,2,3,4,5,6};
int v[] = {0,3,4,8,6,9};
how_to_invest(my_n,my_all,w,v);
cout<<"最大收益"<<m[my_n][my_all]<<endl;
TraceBack(w,my_n,my_all);
cin.get();
return 0;
}
樹的最大連通分支
給定一棵樹,樹中每個頂點u都有一個權w(u),權可以為負數,現找出樹T的一個聯通子圖使該子圖權之和最大。
先用孩子兄弟節點構造一棵樹,從該樹的葉子節點開始,若該結點的子樹權重為正,則加他到該節點權重上,否則不加,若沒子樹,則該節點的權重即為原權重。遍歷方式是自底向上,層級優先。
設S(i)是以i為頂層節點的聯通子圖對應的最優值,S(i) = w(i) + sum(S(k)) S(k)>0,S(k)為i為根的樹的子樹。最終結果即為Max(S(i))。