【问题标题】:How to derive max amount of items from DP table of Knapsack problem?如何从背包问题的 DP 表中导出最大数量的项目?
【发布时间】:2021-04-12 16:51:13
【问题描述】:

我对 1-0 背包问题的算法进行了一些更改。 它还计算最大计数(我们可以将其放入背包中)。 我正在使用它来查找

weights: 1, 3, 4, 5, target sum: 10
result: 1, 4, 5 (because 1 + 4 + 5 = 10)

weights: 2, 3, 4, 9 target sum: 10
result: 2, 3, 4 (2 + 3 + 4 = 9, max possible sum <= 10)

我使用 2 个 DP 表:一个用于计算最大可能总和 (dp),另一个用于计算最大可能金额 (count)。

问题是:如何从这两个表中导出选择的值?

例子:

weights: [3, 2, 5, 2, 1, 1, 3], target_sum: 10
indexes:  0, 1, 2, 3, 4, 5, 6

dp:
0: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
1: [0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3]
2: [0, 0, 2, 3, 3, 5, 5, 5, 5, 5, 5]
3: [0, 0, 2, 3, 3, 5, 5, 7, 8, 8, 10]
4: [0, 0, 2, 3, 4, 5, 5, 7, 8, 9, 10]
5: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
6: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
7: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

count:
0: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
1: [0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1]
2: [0, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2]
3: [0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 3]
4: [0, 0, 1, 1, 2, 2, 2, 2, 2, 3, 3]
5: [0, 1, 1, 2, 2, 3, 3, 2, 3, 3, 4]
6: [0, 1, 2, 2, 3, 3, 4, 4, 3, 4, 4]
7: [0, 1, 2, 1, 2, 3, 3, 4, 4, 5, 5]

在这里,应该导出重量为[3, 2, 1, 3, 1] 的项目(因为它们有最大可能计数)而不是(例如)[5, 2, 3]

一些符号解释: dp 与原始背包问题中的含义相同:i - 表示物品,j 表示重量。 dp[i][j] 中的值表示总和 j 的所选项目(权重)的总和。

count 中的每个单元格对应于dp,并显示最大可能数量的项目(总重量 = dp[i][j]

如何有效地导出所选项目?

我知道如何通过从右下角的单元格重构它来从 dp 中获取任何项目(例如,不是它们的最大数量)。 此外,我发现了一个 hack,如果输入被排序,它允许派生项目。 但我正在寻找一种可以做到这一点而不会疼痛的方法。 有可能吗?


构造这两个表的代码无关紧要,但这里是:

def max_subset_sum(ws, target_sum):
    n = len(ws)
    k = target_sum

    dp = [[0] * (k + 1) for _ in range(n + 1)]
    count = [[0] * (k + 1) for _ in range(n + 1)]

    for i in range(1, n + 1):
        for j in range(1, k + 1):
            curr_w = ws[i - 1]
            if curr_w > j:
                dp[i][j] = dp[i - 1][j]
                count[i][j] = count[i - 1][j]
            else:
                tmp = round(dp[i - 1][j - curr_w] + curr_w, 2)
                if tmp >= dp[i - 1][j]:
                    dp[i][j] = tmp
                    count[i][j] = count[i - 1][j - curr_w] + 1
                else:
                    dp[i][j] = dp[i - 1][j]
                    count[i][j] = count[i - 1][j]

    return get_items(dp, k, n, ws)


def get_items(dp, k, n, ws):
    # The trick which allows to get max amount of items if input is sorted
    start = n
    while start and dp[start][k] == dp[start - 1][k]:
        start -= 1


    res = []
    w = dp[start][k]
    i, j = start, k
    while i and w:
        if w != dp[i - 1][j]:
            res.append(i - 1)
            w = round(w - ws[i - 1], 2)
            j -= ws[i - 1]
        i -= 1

    return res

另外,我有奇怪的尝试来获得最大数量的物品。 但它会产生不正确的结果,总和为9:[3, 1, 1, 2, 2]

def get_items_incorrect(dp, count, k, n, ws):
    start = n

    res = []
    w = dp[start][k]
    i, j = start, k
    while i and w:
        # while dp[i][j] < ws[i - 1]:
        #     i -= 1
        while ws[i - 1] > j:
            i -= 1
        if i < 0:
            break

        max_count = count[i][j]
        max_count_i = i

        while i and w == dp[i - 1][j]:
            if count[i - 1][j] > max_count:
                max_count = count[i - 1][j]
                max_count_i = i - 1
            i -= 1

        res.append(max_count_i - 1)
        w = round(w - ws[max_count_i - 1], 2)
        j -= ws[max_count_i - 1]

        i = max_count_i - 1

    return res

抱歉,阅读时间长,感谢您的帮助!

【问题讨论】:

    标签: algorithm dynamic-programming knapsack-problem


    【解决方案1】:

    看来你把问题复杂化了。这种问题(没有项目成本)可以使用一维列表来解决。最佳情况的权重存储在并行列表中。

    表格填充后,我们寻找最大占用索引(最大可能的总和

    def sol(wts, target):
        dp = [-1] * (target + 1)
        dp[0] = 0
        items = [0] * (target + 1)
        wts.sort()
    
        for w in weights:
            for i in range(target, w-1, -1):
                if (dp[i-w] >= 0) and (dp[i-w] > dp[i]):
                    dp[i] = dp[i-w] + 1
                    items[i] = w
    
        last = target
        while (last) and  (dp[last] < 0):
            last -= 1
    
        best = dp[last]
        res = []
        while (last):
            res.append(items[last])
            last -= items[last]
    
        return(best, res)
    
    weights = [1, 2, 3, 3, 5, 2, 1]
    target_sum = 9
    print(sol(weights, target_sum))
    

    【讨论】:

    • @Nikita Emm,是的,我失去了排序。在我的本地代码中,它被注释了,所以我错误地认为它可能会被省略。
    • 首先我不得不说这是一个了不起的算法!如果我没记错的话,它需要 O(n) 空间,而问题帖子中的算法需要 O(nk) 空间(时间复杂度相同)。但是有一个问题:要找到它仅适用于排序输入的最大可能数量的项目,请考虑:` weights = [1, 2, 3, 3, 5, 2, 1] target_sum = 9 print(sol(weights, target_sum )) # (4, [3, 3, 2, 1]) print(sol(sorted(weights), target_sum)) # 5, [3, 2, 2, 1, 1]) `
    • 对不起,我已经删除了评论并再次尝试重新格式化代码:)
    • 那么您怎么看,是否可以在不对输入进行排序的情况下解决它?
    • 昨天我认为排序没有必要。我应该详细说明。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-25
    相关资源
    最近更新 更多