1. 程式人生 > >分治與動態規劃(3種揹包問題)

分治與動態規劃(3種揹包問題)

動態規劃、分治法和貪心法都是利用求解子問題,而後利用子問題求解更上層問題,最終獲得全域性解決方案的方法。但是三者的應用場景和性質卻存在著極大的不同:

1. 分治法

分治法的精髓:

分–將問題分解為規模更小的子問題;
治–將這些規模更小的子問題逐個擊破;
合–將已解決的子問題合併,最終得出“母”問題的解;

很容易將分治法與動態規劃相混淆,但兩者卻有著本質上的差異。分治法採用的是遞迴的思想來求解問題,兩個分解的子問題獨立求解,其之間無任何的重疊。而上一層問題只需要對兩個子問題進行一定的合併即可得到答案。即s(t)= s(sub1)+s(sub2),而s(sub1)s(sub2)之間無任何重疊。

1.1 使用分治法求陣列中的最大值

函式將陣列a[1],……,a[r]分成兩部分,分別求出每部分的最大元素(遞迴地),並返回較大的那一個作為整個陣列的最大元素。

int a[];
...

int max(int l,int r) {
    if (l==r) return a[l];//如果只有一個元素,就返回它

    int m=(l+r)/2;
    int u = max(l,m);
    int v = max(m+1,r);

    return (u>v) ? u : v;
}

2. 動態規劃

該種方法較為複雜,但十分有用和高效,其核心性質是當前問題的答案s(t)並不能單獨由s(t-1)求得,還有可能需要使用到s(1)...s(t-1)

。具體需要使用到那些,是由問題本身的性質所決定的(常常是一個約束,或變相的約束)。
動態規劃分解後的子問題相互間有聯絡,有重疊的部分。

2.1 01揹包問題

01揹包問題:
一個揹包總共可以裝下重量為W的東西,現在有N個物品,第i個物品的重量為weight[i],其價值為value[i],現在往揹包裡面裝東西,希望能夠使得揹包裝下的物品的總價值最大。
DP要求先找出子問題,我們可以這樣考慮:在物品比較少,揹包容量比較小時怎麼解決?用一個數組dp[i][j]表示,在只有i個物品,容量為j的情況下揹包問題的最優解,那麼當物品種類變大為i+1時,最優解是什麼?第i+1個物品可以選擇放進揹包或者不放進揹包(這也就是0和1),假設放進揹包(前提是放得下),那麼dp[i+1][j]=dp[i][j-weight[i+1]]+value[i+1]

;如果不放進揹包,那麼f[i+1][j]=f[i][j]
這就得出了狀態轉移方程:
f[i+1][j]=max(f[i][j],f[i][j-weight[i+1]+value[i+1])
完整程式碼:

#define V 1500
int dp[10][V];//全域性變數,自動初始化為0
int weight[10];
int value[10];
#define  max(x,y)   (x)>(y)?(x):(y)
int main() {
    int N,M;
    cin>>N;//物品個數
    cin>>M;//揹包容量
    for (int i=1;i<=N; i++) {
        cin>>weight[i]>>value[i];
    }
    for (int i=1; i<=N; i++) 
        for (int j=1; j<=M; j++) {
            if (weight[i]<=j) 
                dp[i][j] = max(f[i-1][j],f[i-1][j-weight[i]]+value[i]);
            else
                dp[i][j] = dp[i-1][j];
        }

    cout<<dp[N][M]<<endl;

}

可以將dp[i][j]這個二維陣列優化成一維陣列。
另外,還有多重揹包和完全揹包問題,可以見連結

優化,以及01揹包和完全揹包問題的區別

有趣的是,當我們將二維陣列優化成一維陣列時,01揹包和完全揹包問題的解法幾乎完全一樣,只有一句需要改變。
01揹包的解:

int main() {
    int n,m;
    while(cin>>n>>m) {
        vector<int> weight(n+1,0);//物品的重量
        vector<int> value(n+1,0);//物品的價值
        vector<int> dp(m+1,0);//一維陣列,存放用n個產品裝容量為i的揹包的解
        for(int i=0;i<n;i++)
            cin>>weight[i+1]>>value[i+1];//輸入

        for(int i=1;i<=n;i++)
            //這裡是逆序
            //用j>=weight[i]作為判斷條件,省略了一個if語句
            for(int j=m;j>=weight[i];j--) {
                    dp[j]=max(dp[j],dp[j-weight[i]]+value[i]);

        cout<<dp[m]<<endl;
    }
    return 0;
}

這裡,只用到了一維陣列,但是通過採用逆序的方法,使得效果是相同的。

然後是完全揹包問題的解,只需要將上面的逆序改為順序:

for(int i=1;i<=n;i++)
        //這裡變成了順序
        //這裡設j從weight[i]開始,省略了一個if語句
        for(int j=weight[i];j<=m;j++)
                dp[j]=max(dp[j],dp[j-weight[i]]+value[i]);