zoukankan      html  css  js  c++  java
  • 【算法分析】实验 3. 基于动态规划方法求解0-1背包问题

    实验内容

        本实验要求基于算法设计与分析的一般过程(即待求解问题的描述、算法设计、算法描述、算法正确性证明、算法分析、算法实现与测试),在针对0-1背包问题求解的实践中理解动态规划 (Dynamic Programming, DP) 方法的思想、求解策略及步骤。

        作为挑战:可以考虑基于跳跃点的改进算法,以及对连续型物品重量/背包容量的支持。

    实验目的

    • 理解动态规划方法的核心思想以及动态规划方法的求解过程;
    • 从算法分析与设计的角度,对0-1背包问题的基于DP法求解有更进一步的理解。

    实验结果

    步骤1

        理解问题,给出问题的描述。

        n个物体,1个背包。对物品i,其价值为(v_i),重量为(W_i),背包的容量为 (W),如何选取物品,是的背包中装入的物品的总价值最大?

        在约束条件为:选取物品的重量小于等于背包重量的情况下,尽可能让背包中物品的总价值最大。

        根据问题描述,设计如下的约束条件和目标函数:

    约束条件:

    [egin{equation} left{ egin{array}{**lr**} sum_{i=1}^{n} w_ix_i leq W \ x_i in {0,1}, 1 leq i leq n & end{array} ight. end{equation} ]

    目标函数:

    [maxsum_{i=1}^{n} v_ix_i ]

        问题现在等价于,寻找一个在满足约束条件情况下,并使目标函数达到最大的解 (X=(x_1,x_2,...,x_n))

    步骤2

        算法设计,包括策略与数据结构的选择

        设计用二维数组对物品信息进行记录: (C[i][j]) 用来记录如果当前还有(i)个物品,背包容量还剩(j)的情况下,当前背包所能得到的最大价值。

    很容易发现条件即:$$C[0][j] = C[i][0] = 0$$

    递归定义应该为:

    [\ C[i][j]= egin{equation} left{         egin{array}{**lr**}         C[i-1][j], & j < w_i \ max{C[i-1][j],C[i-1][j-w_i]+v_i}, & j ge w_i end{array}   ight.   end{equation} ]

        可以这样理解,每个物品我可以选择是否加入到背包中,首先判断,当前物品是否重量已经大于背包所能容纳的重量;如果能容纳该物体,则进行判断加入该物品(C[i-1][j-w_i]+v_i)得到的价值更高,还是不加入该物品(C[i-i][j])所能得到的物品的总价值更高。

    步骤3

        描述算法。希望采用源代码以外的形式,如伪代码或流程图等;

    伪代码表示:

    01PACKAGE(n,w,v,W) // n为物品的个数,w为重量, v为价值,W为背包容量
    	// C[1..n,1..n]为最优解
    	for i=1 to n:
    		do C[i][0] = 0
    	for j=1 to W:
    		do C[0][j] = 0
    	for i=1 to n:
    		for j=1 to W:
    			do
    			if j < w[i]:
    			then C[i][j]=C[i-1][j]
    			else
    			then C[i][j]=max{C[i-1][j],C[i-1][j-w[i]]+v[i]}
    	return C
    

    步骤4

        算法的正确性证明。需要这个环节,在理解的基础上对算法的正确性给予证明;

    对该算法进行最优子结构的证明:

    假设(X=(x_1,x_2,x_3,...,x_n))是背包问题的最优解,那么((x_2,..,x_n))是下面问题的一个最优解:

    [egin{equation} left{ egin{array}{**lr**} sum_{i=2}^{n} w_ix_i leq W-w_1x_1 \ x_i in {0,1}, 2 leq i leq n & end{array} ight. end{equation} ]

    目标函数: $$maxsum_{i=2}^nv_ix_i$$

    即除去第一个物品以后的子问题

    证明如下:(反证法)

    (X=(x_2,...,x_n))不是上述子问题的最优解,设(Y=(y_2,...,y_n))是上述问题的最优解,则(Y)所求的目标函数的值一定比X求得的目标函数的值更大,即:

    [sum_{i=2}^nv_iy_i > sum_{i=2}^nv_ix_i ]

    (Y)满足约束条件:(sum_{i=2}^nw_iy_i leq W-w_1x_1),即 (w_1x_1+sum_{i=2}^nw_iy_i leq W),该不等式证明((x_1,y_1,y_2,...,y_n))是原问题的一个解。

    在公式 ((5)) 中左右同时加上(v_1x_1),可得:$$ v_1x_1+sum_{i=2}^nv_iy_i > v_1x_1+sum_{i=2}^nv_ix_i$$,说明((x_1,y_1,y_2,...,y_n))要比((x_1,x_2,...,x_n)) 方案价值更高,所以((x_1,x_2,...,x_n))不是最优解,产生了矛盾。

    所以其最优子结构的性质得证。

    步骤5

        算法复杂性分析,包括时间复杂性和空间复杂性;

    • 求解0-1背包问题部分

    时间复杂性分析:

    由于只需要遍历进行,所以只需要考虑循环中的复杂度即可。

    [T(n) =O(n)+O(W)+O(nW) \ =O(nW) ]

    空间复杂度,主要是生成数组时占用的空间。

    [T(n)=O(n^2) ]

    • 得到最优解部分

    时间复杂度分析:

    只需要一个循环即可。

    [T(n)=O(n) ]

    空间复杂度为:$$O(1)$$

    步骤6

        算法实现与测试。附上代码或以附件的形式提交,同时贴上算法运行结果截图;

    # -*- coding: utf-8 -*-
    """
    Created on Fri Sep 28 12:44:40 2018
    @theme: 算法准备-01背包问题
    @author: pprp
    """
    
    import numpy as np
    
    def solvePackage(n,w,v,W):
        """solve the 01 package problem"""
    
        C=np.zeros((n+1,W+1))
        
        for i in range(n):
            C[i][0]=0
        for i in range(W):
            C[0][i]=0
    
        for i in range(1,n+1):
            for j in range(1,W+1):
                if j < w[i-1]:
                    C[i][j]=C[i-1][j]
                else:
                    C[i][j]=max(C[i-1][j],C[i-1][j-w[i-1]]+v[i-1])
        return C
            
    def getSolution(n,w,W,C):
        j = W
        x = np.zeros(n)
        for i in range(n,0,-1):
            if C[i][j]==C[i-1][j]:
                x[i-1] = 0
            else:
                x[i-1] = 1
                j -= w[i-1]
        return x
    if __name__ == "__main__":
        n = 11
        w = np.array([2, 6, 3, 4, 2, 8, 2, 4, 7, 5, 1])
        v = np.array([10,23,5,34,23,17,22,32,12,15,32])
        W = 15
    
        C=solvePackage(n,w,v,W)
        x=getSolution(n,w,W,C)
    
        print("packages:",x)
    
        print("Output:
    ",C)
    

    实验结果

    packages: [1. 0. 0. 1. 1. 0. 1. 1. 0. 0. 1.]
    

    验证结果:10+34+23+22+32+32=153

    实验总结

    动态规划基本思想

        动态规划算法通常是用来解决某种最优性质的问题。基本思想是将带求解问题划分为若干个子问题,先求解子问题,然后从子问题的解得到原问题的解。动态规划与分治法的区别在于,动态规划的子问题可能是互相重叠的重复计算的,分治法则是相互独立的。可以用一个表来记录子问题是否已经求解,这样可以避免重复求解。

    动态规划应用条件

        需要满足最优化原理、无后效性和重叠性。

    • 最优化原理,一个最优化策略的子策略一定是最优的,就是满足最优子结构的性质。

    • 无后效性,一个阶段以前各阶段的状态无法直接硬性它未来的决策,只能通过当前的这个状态。

    • 重叠性,就是记录已经解决过的问题,需要存储已经解决过的问题,空间复杂度比较大,是一种以空间换时间的算法。

    难点

        动态规划的难点在于,如何根据问题的最优子结构的性质,构造动态规划方法中的递归公式或动态规划方程。就比如本问题中,如何设计这个方程才是难点所在。

    遇到的问题

        在逆向求解使用的哪几个背包的时候,由于对问题理解的不深刻,导致汇总的时候发现计算结果出现了问题,也就是(getSolution)这个函数出现了问题。应该逆向进行求解问题,也就是从后往前进行推导,更改了循环的方向以后就可以得到最终的结果了。

    心得

        只有在真正理解算法的基础上,然后加以伪代码的梳理,这时候写才能一气呵成。另外还需要对代码计算出来的结果进行人工核查,防止某些问题被忽略掉。另外这个问题已经被老师分析的比较透彻,所以写起来没有太大的困难。但是遇见新的问题的时候,如何构造解决问题的方法才是难点所在。

  • 相关阅读:
    移动端rem布局的适配mixin【转藏】
    移动端布局Demo展示图文
    百思不得其解—这些年做Web开发遇到的坑?
    elemetnui 分页..解决 bug
    linq.js
    yalinqo 的使用...
    vue 利用 v-model 实现 双向传递数据..
    Mui 选项卡 tab 高度 没有自适应....
    css flex 使内容 水平居中 的方法...
    IDEA 在 专注模式下 显示 行号 和 缩进线...
  • 原文地址:https://www.cnblogs.com/pprp/p/9880045.html
Copyright © 2011-2022 走看看