首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

01 背包問題:遞歸回溯法 C 語言完整實現與優化分析

     編輯:關於C語言

01 背包問題:遞歸回溯法 C 語言完整實現與優化分析

01 背包是經典動態規劃問題。本文提供遞歸回溯基礎實現、狀態空間樹分析、備忘錄優化、動態規劃底向上實現、空間優化版本,完整覆蓋從指數級到多項式級的演進路徑。

一、問題定義

  • 輸入:n 件物品,重量 w[i],價值 v[i];背包容量 C
  • 決策:每件物品裝或不裝 (0/1)
  • 目標:最大化總價值,總重量 ≤ C

二、遞歸回溯基礎實現 (原文代碼優化版)

#include 
#include 

#define MAX_N 20

int w[MAX_N], v[MAX_N], choose[MAX_N];
int n, C, best_value = 0;
int best_choose[MAX_N];

// 遞歸搜索:考慮第 idx 件物品,剩余容量 rest
void dfs(int idx, int rest, int cur_value) {
    if (idx == n) {  // 葉子節點
        if (cur_value > best_value) {
            best_value = cur_value;
            for (int i=0; i

三、複雜度分析與狀態空間樹

                idx=0
              /       \
           不裝        裝 (w[0]≤C)
            / \         / \
          ... ...     ... ...
深度 n,每層 2 分支 → 2^n 葉子節點
時間複雜度:O(2^n) - 指數級,n>30 不可行

四、優化 1:備忘錄 (自頂向下 DP)

狀態定義:memo[idx][rest] = 從 idx 開始、剩余容量 rest 可獲得的最大價值

int memo[MAX_N][MAX_C+1];  // -1 表示未計算
int visited[MAX_N][MAX_C+1];

int dfs_memo(int idx, int rest) {
    if (idx == n) return 0;
    if (visited[idx][rest]) return memo[idx][rest];
    
    int res = dfs_memo(idx+1, rest);  // 不裝
    if (w[idx] <= rest) {
        int take = v[idx] + dfs_memo(idx+1, rest - w[idx]);
        if (take > res) res = take;
    }
    
    visited[idx][rest] = 1;
    return memo[idx][rest] = res;
}

複雜度:狀態數 n × C,每狀態 O(1) → O(nC) 時間、O(nC) 空間

五、優化 2:動態規劃底向上 (迭代版)

int dp[MAX_N+1][MAX_C+1];

// dp[i][j] = 前 i 件物品、容量 j 的最大價值
for (int i=1; i<=n; i++) {
    for (int j=0; j<=C; j++) {
        dp[i][j] = dp[i-1][j];  // 不裝第 i 件
        if (w[i-1] <= j) {
            int take = v[i-1] + dp[i-1][j - w[i-1]];
            if (take > dp[i][j]) dp[i][j] = take;
        }
    }
}
// 答案:dp[n][C]

六、優化 3:空間優化至一維數組 (核心技巧)

int dp[MAX_C+1] = {0};

// 關鍵:逆序遍歷容量,避免覆蓋上一輪數據
for (int i=0; i=w[i]; j--) {  // 必須逆序!
        int take = v[i] + dp[j - w[i]];
        if (take > dp[j]) dp[j] = take;
    }
}
// 答案:dp[C]

為什麼逆序? dp[j] 依賴 dp[j-w[i]] (上一輪狀態),正序會提前覆蓋導致「重複裝入」。

七、方案重構:記錄選擇路徑

// 一維 DP 方案重構
int choice[MAX_N][MAX_C+1];  // 記錄第 i 件、容量 j 是否選擇

for (int i=0; i=w[i]; j--) {
        int take = v[i] + dp[j - w[i]];
        if (take > dp[j]) {
            dp[j] = take;
            choice[i][j] = 1;
        }
    }
}

// 回溯構造方案
int rest = C;
for (int i=n-1; i>=0; i--) {
    if (choice[i][rest]) {
        choose[i] = 1;
        rest -= w[i];
    } else {
        choose[i] = 0;
    }
}

八、完整工程級實現 (含輸入驗證、大數組動態分配)

#include 
#include 

int main() {
    int n, C;
    if (scanf("%d %d", &n, &C) != 2) return 1;
    if (n <= 0 || C <= 0) return 1;
    
    int *w = malloc(n * sizeof(int));
    int *v = malloc(n * sizeof(int));
    for (int i=0; i=w[i]; j--) {
            int take = v[i] + dp[j - w[i]];
            if (take > dp[j]) {
                dp[j] = take;
                choice[i*(C+1) + j] = 1;
            }
        }
    }
    
    printf("%d\n", dp[C]);
    
    // 可選:輸出方案
    int rest = C;
    for (int i=n-1; i>=0; i--) {
        if (choice[i*(C+1) + rest]) {
            printf("Item %d taken\n", i);
            rest -= w[i];
        }
    }
    
    free(w); free(v); free(dp); free(choice);
    return 0;
}

九、算法選型指南

場景推薦算法複雜度備註
n ≤ 20, C 任意遞歸回溯 + 剪枝O(2^n)實現簡單,適合小規模
n ≤ 1000, C ≤ 10^5一維 DPO(nC) 時間, O(C) 空間標準解法
n ≤ 10^5, C ≤ 10^9價值為狀態 DP / 貪心近似O(nV) 或 O(n log n)需根據數據特點選擇
需輸出方案記錄 choice 回溯額外 O(nC) 空間或二維 DP 直接回溯

十、擴展:完全背包、多重背包、分組背包

  • 完全背包:正序遍歷 for(j=w[i]; j<=C; j++)
  • 多重背包:二進制拆分轉 01 背包,或單調隊列優化
  • 分組背包:組內互斥,for(j=C; j>=0; j--) for(k在組內) dp[j]=max(dp[j], dp[j-w[k]]+v[k])

總結

01 背包從遞歸回溯 O(2^n) → 備忘錄 O(nC) → 底向上 DP O(nC) → 一維 DP O(C) 空間,體現了「狀態定義 → 狀態轉移 → 實現優化」的動態規劃核心思想。一維逆序遍歷是空間優化的關鍵技巧,**必須理解依賴關係才能正確應用**。實際工程中根據 n、C、數據分佈選擇合適算法,並注意 long long 防溢出、動態分配防棧溢出。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved