java數據結構與算法總結(五)--揹包問題c詳解:01揹包、完全揹包、多重揹包

本文來自

只瞭解0-1揹包問題。可點擊該鏈接

想了解三種揹包問題之間的公式以及公式之間有什麼區別。請點擊該鏈接

若想了解沒種揹包問題具體java代碼怎麼寫。可點擊該鏈接

參考鏈接:

  1. http://www.cnblogs.com/fengty90/p/3768845.html
  2. http://blog.csdn.net/mu399/article/details/7722810
  3. http://blog.csdn.net/xiaowei_cqu/article/details/8191808
  4. http://blog.csdn.net/insistgogo/article/details/11176693

揹包問題是動態規劃算法的一個典型實例,首先介紹動態規劃算法:

動態規劃:

     1、基本思想:


動態規劃算法通常用於求解具有某種最優性質的問題。在這類問題中, 可能會有很多可行解。沒一個解都對應於一個值,我們希望找到具有最優值的解。胎動規劃算法與分治法類似,其基本思想也是將待求解問題分解爲若干個子問題,先求解子問題,然後從這些子問題的解得到原問題的解。與分治法不同的是,適用於動態規劃算法求解的問題,經分解得到的子問題往往不是互相獨立的。若用分治法來解這類問題,則分解得到的子問題數目太多,有些子問題被重複計算很多次。如果我們能保存已解決子問題的答案,而在需要時再找出已求得的答案,這樣就可以避免大量的重複計算,節省時間。我們可以用一個表來記錄所有已解決的子問題的答案。不管該子問題以後是否被用到,只要它被計算過,就將其結果填入表中。這就是動態規劃算法的基本思路。具體的動態規劃算法多種多樣,但它們具有相同的填表格式。

與分治法最大的差別是:適用於動態規劃求解的問題,經分解後得到的子問題往往不是互相獨立的(即下一個子階段的求解是建立在上一個子階段的解的基礎上,進行進一步的求解)

     1、應用場景:
適用於動態規劃的問題必須滿足最優化原理、無後效性和重疊性。

(1) 最優化原理(最優子結構性質):一個最優化策略具有這樣的性質,不論過去狀態和決策如何,對前面的決策所形成的狀態而言,餘下的決策必須構成最優策略。簡而言之,一個最優化策略的子策略總是最優的。一個問題滿足最優化原理又稱其具有最優子結構性質。

(2) 無後效性:將各階段按照一定的次序排列好之後,對於某個給定的階段狀態,它以前各階段的狀態無法直接影響它未來的決策,而只能通過當前的這個狀態。換句話說,每個狀態都是過去歷史的一個完整總結。這就是無後向性,又稱無後效性。

(3) 子問題的重疊性:動態規劃將原來具有指數級時間複雜度的搜索算法改進成了具有多項式時間複雜度的算法。其中的關鍵在於解決冗餘,這就是動態規劃算法的根本目的。動態規劃實質上是一種以空間換時間的技術,它在實現的過程中,不得不存儲產生過程中的各種狀態,所以它的空間複雜度要大於其他算法。

01揹包問題:

01揹包問題描述:有編號分別爲a,b,c,d,e的五件物品,它們的重量分別是2,2,6,5,4,它們的價值分別是6,3,5,4,6,每件物品數量只有一個,現在給你個承重爲10的揹包,如何讓揹包裏裝入的物品具有最大的價值總和?

動態規劃的基本思路:將該問題轉換成子問題,考慮五件物品在給定承重 C 的揹包下最大價值爲原問題,如下表所示,即爲考慮abcde,C = 10時的最大價值,假設爲f[5][10],原問題的解可以分解爲兩種情況,第一種情況是不考慮放入a只考慮放入bcde承重爲C時的最大價值f[4][C],第二種情況是考慮放入a時的最大價值,即value[a]+f[4][10-weight[a]]。 原問題的解f[5][10]取上述兩種情況中的最大值,即f[5][10] = max{f[4][10], (f[4][10-weight[a]+value[a]))}。 由此可以看出裏面涉及到需要計算f[4][10]和f[4][10-weight[a]]即f[4][4]等子問題。 以此類推,自頂向下的分析可以看出原問題需要子問題的解,我們需要先計算出子問題的解,自底向上求解。求解方式如下表所示,順序是自底向上、從左往右,或者從左往右、自底向上都可以。注意此問題中的abcde可以包含相同的物件,它們之間的順序也可以是任意的,不影響最終的結果。

name     weight     value     0 1 2 3 4 5 6 7 8 9 10  
a 2 6 0 0 6 6 9 9 12 12 15 15 15  
b 2 3 0 0 3 3 6 6 9 9 9 10 11  
c 6 5 0 0 0 0 6 6 6 6 6 10 11  
d 5 4 0 0 0 0 6 6 6 6 6 10 10  
e 4 6 0 0 0 0 6 6 6 6 6 6 6  


按上述描述的遞推公式計算時,我們需要初始值,f[i][0]=0(1<=i<=5), f[1][j]=(j < weight[e])?0:value[e],(1<=j<=10)。代碼如下:

#include <iostream>
using namespace std;


int knapsack(int *W, int *V, int *res, int n, int C)
{
    int value = 0;
    int **f = new int*[n];
    for(int i = 0; i < n; i++)
    {
        f[i] = new int[C+1];
    }

    for(int i = 0; i < n; i++)
        for(int j = 0; j <= C;j++)
            f[i][j] = 0;

    for(int i = 0; i < n; i++)
    {
        f[i][0] = 0;
    }
    for(int i = 1; i <= C; i++)
    {
        f[0][i] = (i < W[0])?0:V[0];
    }
    for(int i = 1; i < n; i++)
    {
        for(int y = 1; y <= C; y++)
        {
            if(y >= W[i])
            {
                f[i][y] = (f[i-1][y] > (f[i-1][y-W[i]] + V[i]))?f[i-1][y]:(f[i-1][y-W[i]] + V[i]);
            } else {
                f[i][y] = f[i-1][y];
            }
        }
    }

    for(int i = 0; i < n; i++)
    {
        for(int j = 0; j < C+1;j++)
           cout << f[i][j] << " ";
        cout << endl;
    }

    value = f[n-1][C];
    int j = n-1;
    int y = C;
    while(j)
    {
        if(f[j][y] == (f[j-1][y-W[j]] + V[j]))
        {
            res[j] = 1;
            y = y - W[j];
        }
        j--;
    }
    if(f[0][y])
    {
        res[0] = 1;
    }

    for(int i = 0; i < n;i++)
    {
        delete f[i];
        f[i] = 0;
    }
    delete [] f;
    f = 0;
    return value;
}

void test1()
{
    int n, C;
    while(cin >> n >> C)
    {
        int *W = new int[n];
        int *V = new int[n];
        int *res = new int[n];
        for(int i = 0; i < n; i++)
        {
            res[i] = 0;
        }
        int w = 0, v = 0, i = 0;
        while(i < n)
        {
            cin >> w >> v;
            W[i] = w;
            V[i] = v;
            i++;
        }
        int value = knapsack(W, V, res, n, C);
        cout << value << endl;
        for(int i = 0; i < n; i++)
            cout << res[i] << " ";
        cout << endl;
        delete W;
        delete V;
        delete res;
    }
}

int main()
{
    test1();
    return 0;
}



輸入示例:

5 10      //5種物品,揹包承重爲10
2 6       //第一種物品,重量爲2,價值爲1,數目1個
2 3
6 5
5 4
4 6


輸出示例:

0 0 6 6 6 6 6 6 6 6 6         //只放入第一種物品,承重爲0-10的最優值結果
0 0 6 6 9 9 9 9 9 9 9         //放入第一種和第二種物品,承重爲0-10的最優值結果
0 0 6 6 9 9 9 9 11 11 14      //放入第一種、第二種和第三種物品,承重爲0-10的最優值結果
0 0 6 6 9 9 9 10 11 13 14     //放入前四種物品,承重爲0-10的最優值結果
0 0 6 6 9 9 12 12 15 15 15    //放入五種物品,承重爲0-10的最優值結果
15              //原問題的解,放入五種物品,承重爲10的最優值結果
1 1 0 0 1       //揹包中放入第一種、第二種、第五種物品時價值最高,1*6+1*3+0*5+0*4+1*6 = 15


完全揹包問題:

完全揹包問題描述:有編號分別爲a,b,c,d的四件物品,它們的重量分別是2,3,4,7,它們的價值分別是1,3,5,9,每件物品數量無限個,現在給你個承重爲10的揹包,如何讓揹包裏裝入的物品具有最大的價值總和?

完全揹包問題與01揹包問題的區別在於每一件物品的數量都有無限個,而01揹包每件物品數量只有一個。

問題解法其實和01揹包問題一樣,只是初始化的值和遞推公式需要稍微變化一下。初始化時,當只考慮一件物品a時,f[1][j] = j/weight[a]。 遞推公式計算時,f[i][y] = max{f[i-1][y], (f[i][y-weight[i]]+value[i])},注意這裏當考慮放入一個物品 i 時應當考慮還可能繼續放入 i,因此這裏是f[i][y-weight[i]]+value[i], 而不是f[i-1][y-weight[i]]+value[i]

name     weight     value     0 1 2 3 4 5 6 7 8 9 10
d 7 9 0 0 1 3 5 5 6 9 10 10 12
c 4 5 0 0 1 3 5 5 6 8 10 10 11
b 3 3 0 0 1 3 3 4 6 6 7 9 9
a 2 1 0 0 1 1 2 2 3 3 4 4 5


代碼如下:

#include <iostream>
using namespace std;


int knapsack_complete(int *W, int *V, int *res, int n, int C)
{
    int value = 0;
    int **f = new int*[n];
    for(int i = 0; i < n; i++)
    {
        f[i] = new int[C+1];
    }

    for(int i = 0; i < n; i++)
    {
        for(int j = 0; j < C+1; j++)
            f[i][j] = 0;
    }
    for(int y = 1; y < C+1; y++)
    {
        f[0][y] = (y < W[0])?0:((y/W[0])*V[0]);
    }

    for(int i = 1; i < n; i++)
    {
        for(int y = 1; y < C+1; y++)
        {
            if(y < W[i])
            {
                f[i][y] = f[i-1][y];
            } else {
                f[i][y] = (f[i-1][y] > (f[i][y-W[i]]+V[i]))?f[i-1][y]:(f[i][y-W[i]]+V[i]);
            }
        }
    }

    for(int i = 0; i < n; i++)
    {
        for(int j = 0; j < C+1; j++)
            cout << f[i][j] << " ";
        cout << endl;
    }

    value = f[n-1][C];
    int j = n-1;
    int y = C;
    while(j)
    {
        if(f[j][y] == (f[j][y-W[j]]+V[j]))
        {
            res[j]++;
            y = y - W[j];
        }
        j--;
    }
    res[0] = f[0][y]/V[0];

    for(int i = 0; i < n; i++)
    {
        delete f[i];
        f[i] = 0;
    }
    delete [] f;
    f = 0;
    return value;
}
void test1()
{
    int n, C;
    while(cin >> n >> C)
    {
        int *W = new int[n];
        int *V = new int[n];
        int *res = new int[n];
        for(int i = 0; i < n; i++)
        {
            res[i] = 0;
        }
        int i = 0, w, v;
        while(i < n)
        {
            cin >> w >> v;
            W[i] = w;
            V[i] = v;
            i++;
        }
        int value = knapsack_complete(W, V, res, n, C);
        cout << value << endl;
        for(int i = 0; i < n; i++)
            cout << res[i] << " ";
        cout << endl;
        delete W; W = 0;
        delete V; V = 0;
        delete res; res = 0;
    }
}

int main()
{
    test1();
    return 0;
}


輸入示例:

4 10         //4種物品,揹包承重爲10
2 1          //第一種物品,重量爲2,價值爲1,數目不限
3 3
4 5
7 9


輸出示例:

0 0 1 1 2 2 3 3 4 4 5        //只放入第一種物品,承重爲0-10的最優值結果
0 0 1 3 3 4 6 6 7 9 9        //放入第一種和第二種物品,承重爲0-10的最優值結果
0 0 1 3 5 5 6 8 10 10 11     //放入第一種、第二種和第三種物品,承重爲0-10的最優值結果
0 0 1 3 5 5 6 9 10 10 12     //放入四種物品,承重爲0-10的最優值結果
12           //原問題的解,放入四種物品,承重爲10的最優值結果
0 1 0 1      //第一種物品放0個,第二種物品放1個,第三種物品放0個,第四種物品放1個,0*1+1*3+0*5+1*9 = 12


多重揹包問題:

多重揹包問題描述:有編號分別爲a,b,c的三件物品,它們的重量分別是1,2,2,它們的價值分別是6,10,20,他們的數目分別是10,5,2,現在給你個承重爲 8 的揹包,如何讓揹包裏裝入的物品具有最大的價值總和?

多重揹包和01揹包、完全揹包的區別:多重揹包中每個物品的個數都是給定的,可能不是一個,絕對不是無限個。

有兩種解法,解題思路:

作爲一個新問題考慮,由於每個物品多了數目限制,因此初始化和遞推公式都需要更改一下。初始化時,只考慮一件物品a時,f[1][j] = min{num[1], j/weight[1]}。 計算考慮i件物品承重限制爲y時最大價值f[i][y]時,遞推公式考慮兩種情況,要麼第 i 件物品一件也不放,就是f[i-1][y], 要麼第 i 件物品放 k 件,其中 1 <= k <= (y/weight[i]),考慮這一共 k+1 種情況取其中的最大價值即爲f[i][y]的值,即f[i][y] = max{f[i-1][y], (f[i-1][y-k*weight[i]]+k*value[i])}。 這裏爲什麼不能像完全揹包一樣直接考慮f[i][y-weight[i]]+value[i]呢?因爲這樣不容易判斷第 i 件物品的個數是否超過限制數量 num[i]。

name     weight     value     num     0 1 2 3 4 5 6 7 8    
c 2 20 2 0 6 20 26 40 46 52 58 65    
b 2 10 5 0 6 12 18 24 30 36 42 48    
a 1 6 10 0 6 12 18 24 30 36 42 48    


代碼如下:

#include <iostream>
using namespace std;


int knapsack_limitnum(int *W, int *V, int *N, int *res, int n, int C)
{
    int value = 0;
    int **f = new int*[n];
    for(int i = 0; i < n; i++)
    {
        f[i] = new int[C+1];
    }
    for(int i = 0; i < n; i++)
        for(int j = 0; j < C+1; j++)
            f[i][j] = 0;

    for(int y = 1; y < C+1; y++)
    {
        int count = min(N[0], y/W[0]);
        f[0][y] = (y < W[0])?0:(count * V[0]);
    }

    for(int i = 1; i < n; i++)
    {
        for(int y = 1; y < C+1; y++)
        {
            if(y < W[i])
            {
                f[i][y] = f[i-1][y];
            } else {
                int count = min(N[i], y/W[i]);
                f[i][y] = f[i-1][y];
                for(int k = 1; k <= count; k++)
                {
                    int temp = f[i-1][y-W[i]*k] + k*V[i];
                    if(temp >= f[i][y])
                        f[i][y] = temp;
                }
            }
        }
    }

    for(int i = 0; i < n; i++)
    {
        for(int y = 0; y < C+1; y++)
            cout << f[i][y] << " ";
        cout << endl;
    }

    value = f[n-1][C];
    int j = n-1;
    int y = C;
    while(j)
    {
        int count = min(N[j], y/W[j]);
        for(int k = count; k > 0; k--)
        {
            if(f[j][y] == (f[j-1][y-W[j]*k]+k*V[j]))
            {
                res[j] = k;
                y = y - k*W[j];
                break;
            }
        }
        j--;
    }
    res[0] = f[0][y]/V[0];


    for(int i = 0;i < n; i++)
    {
        delete f[i];
        f[i] = 0;
    }
    delete [] f;
    f = 0;
    return value;
}

void test1()
{
    int n, C;
    while(cin >> n >> C)
    {
        int *W = new int[n];
        int *V = new int[n];
        int *N = new int[n];
        int *res = new int[n];
        for(int i =0; i < n; i++)
            res[i] = 0;
        int w, v, n1, i = 0;
        while(i < n)
        {
            cin >> w >> v >> n1;
            W[i] = w;
            V[i] = v;
            N[i] = n1;
            i++;
        }
        int value = knapsack_limitnum(W, V, N, res, n, C);
        cout << value << endl;
        for(int i = 0; i < n; i++)
            cout << res[i] << " ";
        cout << endl;
        delete res; res = 0;
        delete N; N = 0;
        delete V; V = 0;
        delete W; W = 0;
    }
}


int main()
{
    test1();
    return 0;
}


輸入示例:

3 8           //3件物品,揹包承重最大爲8
1 6 10        //第一件物品, 重量爲1,價值爲6, 數目爲10
2 10 5
2 20 2


輸出結果:

0 6 12 18 24 30 36 42 48    //放入第一種物品,承重限制爲0-8的最優值結果
0 6 12 18 24 30 36 42 48    //放入第一種和第二種物品,承重限制爲0-8的最優值結果
0 6 20 26 40 46 52 58 64    //放入三種物品,承重限制爲0-8的最優值結果
64            //原問題的解,在揹包承重爲8中放3種物品最大價值爲64
4 0 2         //最優解對應第一件物品放4個,第二件物品放0個,第三件物品放2個,即4*6+0*10+2*20 = 64


多重揹包的第二種解法,由01揹包的分析可知,01揹包中允許放入的物品有重複,即01揹包中如果考慮要放入的物品的重量和價格相同,不影響最終的結果,因爲我們可以考慮把多重揹包問題中限制數目的物品拆分成單獨的一件件物品,作爲01揹包問題考慮。問題解法和01揹包一致,這裏不再列舉出動態規劃的表格了。
代碼:

#include <iostream>
#include <vector>
using namespace std;


int knapsack_limitnum(vector<int> &W, vector<int> &V, vector<int> &res, int C)
{
    int value = 0;
    int **f = new int*[W.size()];
    for(int i = 0; i < W.size(); i++)
    {
        f[i] = new int[C+1];
    }
    for(int i = 0; i < W.size(); i++)
    {
        f[i][0] = 0;
    }
    for(int i = 1; i < C+1; i++)
    {
        f[0][i] = (i < W[0])?0:V[0];
    }
    for(int i = 1; i < W.size(); i++)
    {
        for(int y = 1; y < C+1; y++)
        {
            if(y < W[i])
            {
                f[i][y] = f[i-1][y];
            } else {
                f[i][y] = (f[i-1][y] > (f[i-1][y-W[i]]+V[i]))?f[i-1][y]:(f[i-1][y-W[i]]+V[i]);
            }
        }
    }
    value = f[W.size()-1][C];
    int j = W.size()-1;
    int y = C;
    while(j)
    {
        if(f[j][y] == (f[j-1][y-W[j]]+V[j]))
        {
            res[j] = 1;
            y -= W[j];
        }
        j--;
    }
    res[0] = (f[0][y])?1:0;
    for(int i = 0; i < W.size(); i++)
    {
        delete f[i];
        f[i] = 0;
    }
    delete [] f;
    f = 0;
    return value;
}

void test1()
{
    int n, C;
    while(cin >> n >> C)
    {
        vector<int> W;
        vector<int> V;
        int *N = new int[n];
        int w, v, n1, i = 0;
        while(i < n)
        {
            cin >> w >> v >> n1;
            N[i] = n1;
            while(n1--)
            {
                W.push_back(w);
                V.push_back(v);
            }
            i++;
        }
        vector<int> res;
        for(int i = 0;i < W.size(); i++)
            res.push_back(0);
        int value = knapsack_limitnum(W, V, res, C);
        cout << value << endl;
        i = 0;
        int j = 0;
        int count = 0, sum = N[i];
        while(j < res.size())
        {
            if(j < sum)
            {
                if(res[j])
                    count++;
            } else {
                cout << count << " ";
                i++;
                sum += N[i];
                count = (res[j])?1:0;
            }
            j++;
        }
        cout << count << endl;
        delete N; N = 0;
    }
}

int main()
{
    test1();
    return 0;
}


輸入示例:

3 8           //3件物品,揹包承重最大爲8
1 6 10        //第一件物品, 重量爲1,價值爲6, 數目爲10
2 10 5
2 20 2


執行結果:

64            //原問題的解,在揹包承重爲8中放3種物品最大價值爲64
4 0 2         //最優解對應第一件物品放4個,第二件物品放0個,第三件物品放2個,即4*6+0*10+2*20 = 64

64            //原問題的解,在揹包承重爲8中放3種物品最大價值爲64
4 0 2         //最優解對應第一件物品放4個,第二件物品放0個,第三件物品放2個,即4*6+0*10+2*20 = 64
————————————————
版權聲明:本文爲CSDN博主「na_beginning」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/na_beginning/article/details/62884939

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章