动态规划之背包问题

栏目: 编程工具 · 发布时间: 5年前

内容简介:背包问题(Knapsack problem)是动态规划的经典问题。动态规划的基础是递归,和分治一样,都是假设子问题已经解决,由子问题的解组合计算得到父问题的解,类似裴波那契数列中的递推式如f(n) = f(n-1) + f(n-2)。但在递归的过程中会出现重复计算子问题的现象,为了避免重复计算,用一个表格记录子问题的结果供查找,从下往上进行递推。找递推式(or 状态转移方程)的思路一般是由最终状态往前回溯,考察解答最终问题需要哪些子问题。背包问题,有很多中类型,常见的有:

背包问题(Knapsack problem)是动态规划的经典问题。动态规划的基础是递归,和分治一样,都是假设子问题已经解决,由子问题的解组合计算得到父问题的解,类似裴波那契数列中的递推式如f(n) = f(n-1) + f(n-2)。但在递归的过程中会出现重复计算子问题的现象,为了避免重复计算,用一个表格记录子问题的结果供查找,从下往上进行递推。找递推式(or 状态转移方程)的思路一般是由最终状态往前回溯,考察解答最终问题需要哪些子问题。

动态规划之背包问题

背包问题,有很多中类型,常见的有:

  • 01背包(ZeroOnePack): 有N件物品和一个容量为V的背包, 每种物品均只有一件 。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。
  • 完全背包(CompletePack): 有N种物品和一个容量为V的背包, 每种物品都有无限件可用 。第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
  • 多重背包(MultiplePack): 有N种物品和一个容量为V的背包, 第i种物品最多有n[i]件可用 。每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

01背包

动态规划的核心过程有两部分,一个是找出问题的“子状态”,再一个就是建立“状态转移方程”(所谓的递推公式)。欲求背包能够获得的总价值,即求前i个物体放入容量为m背包的最大价值c[i][m]

  • w[i]:第i个物体的重量
  • p[i]:第i个物体的价值
  • c[i][j]:前i个物体放入容量为j 包的最大价值
  • c[i-1][j]:前i-1个物体放入容量为j 包的最大价值
  • c[i-1][j-w[i]]:前i-1个物体放入容量为j-w[i] 包的最大价值

以下通过表格来说状态转移方程:

c[i][m]=max{c[i-1][m-w[i]]+p[i],c[i-1][m]}

动态规划之背包问题

这里是用二维数组存储的,可以把空间优化,用一维数组存储。 用c[0..m]表示,c[m]表示把前i件物品放入容量为m的背包里得到的价值。把i从1~n(n件)循环后,最后c[m]表示所求最大值。

这里c[m]就相当于二维数组的c[i][m]。那么,如何得到c[i-1][m]和c[i-1][m-w[i]]+p[i]?

首先要知道,我们是通过i从1到n的循环来依次表示前i件物品存入的状态。即:for i=1..N。现在思考如何能在是c[m]表示当前状态是容量为m的背包所得价值,而又使c[m]和c[m-w[i]]+p[i]标签前一状态的价值?

答案是逆转:

for i=1..N
   for m=V..0
        c[m]=max{c[m],c[m-w[i]]+p[i]};

分析上面的代码:当内循环是逆序时,就可以保证后一个c[m]和c[m-w[i]]+p[i]是前一状态的!这里给大家一组测试数据:

动态规划之背包问题

相关代码实现:

#A naive recursive implementation of 0-1 Knapsack Problem
 
# Returns the maximum value that can be put in a knapsack of
# capacity W
def knapSack(W , wt , val , n):
 
    # Base Case
    if n == 0 or W == 0 :
        return 0
 
    # If weight of the nth item is more than Knapsack of capacity
    # W, then this item cannot be included in the optimal solution
    if (wt[n-1] > W):
        return knapSack(W , wt , val , n-1)
 
    # return the maximum of two cases:
    # (1) nth item included
    # (2) not included
    else:
        return max(val[n-1] + knapSack(W-wt[n-1] , wt , val , n-1),
                   knapSack(W , wt , val , n-1))
 
# end of function knapSack
 
# To test above function
val = [60, 100, 120]
wt = [10, 20, 30]
W = 50
n = len(val)
print knapSack(W , wt , val , n)
 
# This code is contributed by Nikhil Kumar Singh.

以上方法的时间和空间复杂度为O(n*W),其中时间复杂度已经不能再优化了,但是空间复杂度可以优化到O(W),优化后的代码如下:

# A Dynamic Programming based Python Program for 0-1 Knapsack problem
# Returns the maximum value that can be put in a knapsack of capacity W
def knapSack(W, wt, val, n):
    K = [[0 for x in range(W+1)] for x in range(n+1)]
 
    # Build table K[][] in bottom up manner
    for i in range(n+1):
        for w in range(W+1):
            if i==0 or w==0:
                K[i][w] = 0
            elif wt[i-1] <= w:
                K[i][w] = max(val[i-1] + K[i-1][w-wt[i-1]],  K[i-1][w])
            else:
                K[i][w] = K[i-1][w]
 
    return K[n][W]
 
# Driver program to test above function
val = [60, 100, 120]
wt = [10, 20, 30]
W = 50
n = len(val)
print(knapSack(W, wt, val, n))
 
# This code is contributed by Bhavya Jain

上述代码实现为01背包问题的实现原理,如需在实际情况中采用类似的思路解决问题,可以使用Google Optimization Tools工具,具体代码如下:

from ortools.algorithms import pywrapknapsack_solver
 
weights = [[565, 406, 194, 130, 435, 367, 230, 315, 393,
            125, 670, 892, 600, 293, 712, 147, 421, 255]]
capacities = [850]
values = weights[0]
 
# Create the solver.
solver = pywrapknapsack_solver.KnapsackSolver(pywrapknapsack_solver.KnapsackSolver.KNAPSACK_DYNAMIC_PROGRAMMING_SOLVER,
                                              'test')
 
solver.Init(values, weights, capacities)
computed_value = solver.Solve()
 
packed_items = [x for x in range(0, len(weights[0]))
                if solver.BestSolutionContains(x)]
packed_weights = [weights[0][i] for i in packed_items]
 
print("Packed items: ", packed_items)
print("Packed weights: ", packed_weights)
print("Total weight (same as total value): ", computed_value)

参考链接:

完全背包

完全背包(CompletePack): 有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的重量为w [i],价值是p[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。完全背包按其思路仍然可以用一个二维数组来写出:

c[i][m]=max{c[i-1][m-k*c[i]]+k*p[i] | 0<=k*c[i]<=m }

同样可以转换成一维数组来表示:

for i=1..N
    for m=0..V
        c[m]=max{c[m],c[m-w[i]]+p[i]}

完全和01背包的区别,这里的内循环是顺序的,而01背包是逆序的。为何完全背包可以这么写?原因是为了max中的两项是前一状态值。那么这里,我们顺序写,这里的max中的两项当然就是当前状态的值了,为何? 因为每种背包都是无限的。当我们把i从1到N循环时,c[m]表示容量为m在前i种背包时所得的价值,这里我们要添加的不是前一个背包,而是当前背包。所以我们要考虑的当然是当前状态。

参考链接:

多重背包

多重背包(MultiplePack): 有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件重量是w[i],价值是p[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

这题目和完全背包问题很类似。基本的方程只需将完全背包问题的方程略微一改即可,因为对于第i种物品有n[i]+1种策略:取0件,取1件……取n[i]件。令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值,则有状态转移方程:

c[i][m]=max{c[i-1][m-k*w[i]]+k*p[i]|0<=k<=n[i]}

其他参考:


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

复杂网络理论及其应用

复杂网络理论及其应用

汪小帆、李翔、陈关荣 / 清华大学出版社 / 2006 / 45.00元

国内首部复杂网络专著 【图书目录】 第1章 引论 1.1 引言 1.2 复杂网络研究简史 1.3 基本概念 1.4 本书内容简介 参考文献 第2章 网络拓扑基本模型及其性质 2.1 引言 2.2 规则网络 2.3 随机图 2.4 小世界网络模型 2.5 无标度网络模型 ......一起来看看 《复杂网络理论及其应用》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试