1008-----算法筆記----------0-1揹包問題(動態規劃求解)

1.問題描述算法

  給定n種物品和一個揹包,物品i的重量是wi,其價值爲vi,揹包的容量爲C。問:應該如何選擇裝入揹包的物品,使得裝入揹包中物品的總價值最大?數組

2.問題分析spa

  上述問題能夠抽象爲一個整數規劃問題,即求知足 (a)Σwixi ≤ C;(b)xi(0,1),  1≤i≤n;條件下,∑vixi最大時的一個物品xi序列。分析問題能夠發現,該問題具備最優子結構性質,那麼就能夠嘗試用動態規劃方法求解,而動態規劃求解的關鍵就是列出問題的遞歸關係表達式。
blog

  設m(i,j)爲揹包容量爲j,可選物品爲i,i+1,...n時0-1揹包問題的最優質,那麼可有以下遞歸式:遞歸

  m(i,j) = { max( m(i+1, j), m(i+1, j-wi)+vi);   j>=wi;源碼

         { m(i+1, j);               j<wi;string

要求的是m(1,c),此時問題就轉化爲填充m數組的問題了,以n = 5, c = 10, w[] = {2,2,6,5,4},v[] = {6,3,5,4,6},填充的過程以下圖所所示,主要是用上述遞歸式求值,考慮當前物品可否放入,放入當前物品和不放入致使最終的價值哪一個大,圖中陰影部分爲回溯求xi的過程,表示0,1,4號物品被放入揹包中。
io

i/j 1 2 3 4 5 6 7 8 9 10
4 0 0 0 6 6 6 6 6 6 6
3 0 0 0 6 6 6 6 6 10 10
2 0 0 0 6 6 6 6 6 10 15
1 0 3 3 6 6 9 9 9 9 9
0 0 6 6 9 9 12 12 15 15 15

 

 

 

 

 

 

3.源碼table

  3.1 非遞歸class

#include <stdio.h>

#define N 1024
#define max(x, y) (x > y ? x : y)
#define min(x, y) (x < y ? x : y)

void knapsack(int *w, int *v, int c, int n, int (*m)[N]);
void traceback(int *w, int (*m)[N], int *x, int n, int c);




int main(int argc, const char *argv[])
{
    int n, c, w[N], v[N], m[N][N], x[N], i;
    scanf("%d%d", &n, &c);
    for(i = 0; i < n; i++)
        scanf("%d", &w[i]);
    for(i = 0; i < n; i++)
        scanf("%d", &v[i]);

    knapsack(w, v, c, n-1, m);      //這裏傳的是數組的最大下標
    traceback(w, m, x, n-1, c);     //求出是否裝載的序列 x[i]

    printf("Max v: %d\n", m[0][c]);
    for(i = 0; i < n; i++)
        printf("x[%d] = %d\t", i, x[i]);
    printf("\n");

    return 0;
}

void knapsack(int *w, int *v, int c, int n, int (*m)[N]){
    int j, jMax, i;
    jMax = min(w[n] - 1, c);
    for(j = 0; j <= jMax; j++)      //求m[n][]
        m[n][j] = 0;
    for(j = c; j > jMax; j--)
        m[n][j] = v[n];

    for(i = n-1; i > 0; i--){       //依次求m[n-1][] - m[1]
        jMax = min(w[i]-1, c);
        for(j = 0; j <= jMax; j++){
            m[i][j] = m[i+1][j];
        }
        for(j = c; j > jMax; j--){
            m[i][j] = max(m[i+1][j], m[i+1][j-w[i]] + v[i]);
        }
    }

    m[0][c] = m[1][c];              //求m[0][]
    if(w[0] < c){
        m[0][c] = max(m[1][c], m[1][c-w[0]] + v[0]);
    }
}

void traceback(int *w, int (*m)[N], int *x, int n, int c){
    int i;
    for(i = 0; i < n-1; i++){
        if(m[i][c] == m[i+1][c])    //根據m數組 判斷是否裝進去
            x[i] = 0;
        else{
            x[i] = 1;
            c -= w[i];
        }
    }
    x[n] = (m[n][c] > 0) ? 1 : 0;
}

  3.2 遞歸

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define N 5
#define C 10

int w[] = {2, 2, 6, 5,4};           //使用遞歸 避免傳遞太多參數,不清晰
int v[] = {6, 3, 5, 4,6};           //所以設置成全局變量
int m[N][C], x[N];

int knapsack(int i, int j);
void traceback();

int main(int argc, const char *argv[])
{
    int i;
    knapsack(0, C);
    traceback();

    printf("Max :%d\n", m[0][10]);
    for(i = 0; i < N; i++)
        printf("x[%d] = %d\t", i, x[i]);
    printf("\n");

    return 0;
}

int knapsack(int i, int j){
    if(i == N-1){
        m[i][j] = (j > w[i] ? v[i] : 0);
        return  m[i][j];
    }

    int ret1, ret2;
    if(j < w[i]){
        m[i][j] = knapsack(i+1, j);
    }
    else{
        ret1 = knapsack(i+1, j);
        ret2 = knapsack(i+1, j-w[i]) + v[i];
        m[i][j] =  ret1 > ret2 ? ret1 : ret2;
    }
    return m[i][j];

}


void traceback(){
    int i, n = N-1, c = C;
    for(i = 0; i < n; i++){
        if(m[i][c] == m[i+1][c])    //根據m數組 判斷是否裝進去
            x[i] = 0;
        else{
            x[i] = 1;
            c -= w[i];
        }
    }
    x[n] = (m[n][c] > 0) ? 1 : 0;
}

  3.3 書上有種改進的算法,採用跳躍點實現,暫時還沒看懂,也許過兩天在看就懂了呢。

相關文章
相關標籤/搜索