从空间优化的0/1背包实现中重建项目列表

时间:2016-04-25 07:08:24

标签: algorithm dynamic-programming knapsack-problem

0/1背包动态编程算法的空间优化是使用大小等于背包容量的1-d阵列(比方说,A),并在每次迭代时简单地覆盖A [w](如果需要) i,其中A [w]表示考虑前i项并且背包容量为w时的总值。 如果使用此优化,是否有办法重建所选项目列表,可能是通过在DP算法的每次迭代中保存一些额外信息?例如,在Bellman Ford算法中,可以实现类似的空间优化,并且只要我们保留前任指针的列表,即最后一跳(或者首先,取决于源/是否),仍然可以重建最短路径。正在编写面向目标的算法。)

作为参考,这里是我使用动态编程的0/1背包问题的C ++函数,其中我构造了一个二维向量ans,使得ans [i] [j]表示考虑到前i项和背包的总值容量j。我通过反向遍历这个向量来重建所选择的项目:

void knapsack(vector<int> v,vector<int>w,int cap){
 //v[i]=value of item i-1
 //w[i]=weight of item i-1, cap=knapsack capacity
 //ans[i][j]=total value if considering 1st i items and capacity j
 vector <vector<int> > ans(v.size()+1,vector<int>(cap+1));

 //value with 0 items is 0
 ans[0]=vector<int>(cap+1,0);

 //value with 0 capacity is 0
 for (uint i=1;i<v.size()+1;i++){
    ans[i][0]=0;
 }

 //dp
 for (uint i=1;i<v.size()+1;i++) {
    for (int x=1;x<cap+1;x++) {
        if (ans[i-1][x]>=ans[i-1][x-w[i-1]]+v[i-1]||x<w[i-1])
            ans[i][x]=ans[i-1][x];
        else {
            ans[i][x]=ans[i-1][x-w[i-1]]+v[i-1];
        }
    }
 }
 cout<<"Total value: "<<ans[v.size()][cap]<<endl;

 //reconstruction
 cout<<"Items to carry: \n";
 for (uint i=v.size();i>0;i--) {
    for (int x=cap;x>0;x--) {
        if (ans[i][x]==ans[i-1][x]) //item i not in knapsack
            break;
        else if (ans[i][x]==ans[i-1][x-w[i-1]]+v[i-1]) { //item i in knapsack
            cap-=w[i-1];
            cout<<i<<"("<<v[i-1]<<"), ";
            break;
        }
    }
 }
 cout<<endl;
}

3 个答案:

答案 0 :(得分:3)

以下是 yildizkabaran's answer 的 C++ 实现。它采用了 Hirschberg 巧妙的分而治之思想,在 O(nc) 时间和 O(c) 空间中计算具有 n 个物品和容量 c 的背包实例的答案:

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

// Returns a vector of (cost, elem) pairs.
vector<pair<int, int>> optimal_cost(vector<int> const& v, vector<int> const& w, int cap) {
    vector<pair<int, int>> dp(cap + 1, { 0, -1 });

    for (int i = 0; i < size(v); ++i) {
        for (int j = cap; j >= 0; --j) {
            if (w[i] <= j && dp[j].first < dp[j - w[i]].first + v[i]) {
                dp[j] = { dp[j - w[i]].first + v[i], i };
            }
        }
    }

    return dp;
}

// Returns a vector of item labels corresponding to an optimal solution, in increasing order.
vector<int> knapsack_hirschberg(vector<int> const& v, vector<int> const& w, int cap, int offset = 0) {
    if (empty(v)) {
        return {};
    }

    int mid = size(v) / 2;
    auto subSol1 = optimal_cost(vector<int>(begin(v), begin(v) + mid), vector<int>(begin(w), begin(w) + mid), cap);
    auto subSol2 = optimal_cost(vector<int>(begin(v) + mid, end(v)), vector<int>(begin(w) + mid, end(w)), cap);

    pair<int, int> best = { -1, -1 };
    for (int i = 0; i <= cap; ++i) {
        best = max(best, { subSol1[i].first + subSol2[cap - i].first, i });
    }

    vector<int> solution;
    if (subSol1[best.second].second != -1) {
        int iChosen = subSol1[best.second].second;
        solution = knapsack_hirschberg(vector<int>(begin(v), begin(v) + iChosen), vector<int>(begin(w), begin(w) + iChosen), best.second - w[iChosen], offset);
        solution.push_back(subSol1[best.second].second + offset);
    }
    if (subSol2[cap - best.second].second != -1) {
        int iChosen = mid + subSol2[cap - best.second].second;
        auto subSolution = knapsack_hirschberg(vector<int>(begin(v) + mid, begin(v) + iChosen), vector<int>(begin(w) + mid, begin(w) + iChosen), cap - best.second - w[iChosen], offset + mid);
        copy(begin(subSolution), end(subSolution), back_inserter(solution));
        solution.push_back(iChosen + offset);
    }

    return solution;
}

答案 1 :(得分:1)

根据我的理解,通过所提出的解决方案,实际上不可能获得针对特定目标值的所涉及项的集合。可以通过再次生成丢弃的行或维持合适的辅助数据结构来获得该组项。这可以通过将A中的每个条目与生成它的项目列表相关联来完成。但是,这需要比最初提出的解决方案更多的内存。在this期刊论文中也简要讨论了背包问题的回溯方法。

答案 2 :(得分:0)

即使这是一个老问题,我最近也遇到了同样的问题,所以我想我会在这里写下解决方案。您需要的是Hirschberg's algorithm。尽管此算法是为重建编辑距离而编写的,但此处应用的原理相同。这个想法是,当搜索容量为 c n 个项目时,第(n / 2)个项目的背包状态对应于最终最大值值在第一次扫描中确定。我们将此状态称为 weight_m value_m 。可以跟踪大小为 c 的其他 1d数组。因此内存仍为O(c)。然后,问题分为两部分:容量为 weight_m 的项目 0 n / 2 和项目 n / 2 n ,容量为 c-weight_m 。减少的问题总数为 nc / 2 。继续这种方法,我们可以确定每个项目之后的背包状态(占用的重量和当前值),之后我们可以简单地检查以查看其中包括哪些项目。此算法在使用O(c)内存时在O(2nc)中完成,因此就大O而言,即使算法的速度至少慢两倍,也不会改变。我希望这对遇到类似问题的人有所帮助。