美文网首页
动态规划入门总结(未完待续)

动态规划入门总结(未完待续)

作者: 羲牧 | 来源:发表于2020-09-11 08:37 被阅读0次

首先通过求解0-1背包问题的思路演化来引出动态规划的方法。
0-1背包
假设我们有一个最大负重量为9的背包,同时我们有5个物品,每个物品的重量分别为2,2,4,6,3。且物品不可拆卸,也就是说我们要么将一个物品全部放入背包,要么全部不放。那么,请问,我们最大能装多大重量的物品进入背包?

1. 回溯思想求解0-1背包

首先我们用回溯的思想求解这个问题,代码如下。
回溯本质上可以理解为一种穷举所有可能的路径,如果不满足要求,迅速结束此路径的探索。

# 0-1背包问题
weight = [2, 2, 4, 6, 3]
n = 5
max_allowed_weight = 9
cur_max_weight = -1

class ZeroOneBag(object):
    def __init__(self, weight, n, max_allowed_weight, cur_max_weight):
        self.weight = weight
        self.n = n
        self.max_allowed_weight = max_allowed_weight
        self.cur_max_weight = cur_max_weight
    
    def backtracking(self, i, cur_weight, cur_result):
        """
        # 将物品一个一个的做判定,当判定到第i个物品当前背包重量为cur_weight时候的情况
        # 初始调用backtrack(0,0)
        """
        if i == n or cur_weight == self.max_allowed_weight:
            if cur_weight > self.cur_max_weight:
                self.cur_max_weight = cur_weight
            if sum(cur_result) == self.cur_max_weight:
                print('result, cur_max_weight', cur_result, self.cur_max_weight)
            return
        
        # 不加入第i个物品
        cur_result.append(0)
        self.backtracking(i+1, cur_weight, list(cur_result))

        # 若背包重量允许,则加入第i个物品
        if cur_weight + self.weight[i] <= self.max_allowed_weight:
            cur_result.pop(-1)
            cur_result.append(weight[i])
            self.backtracking(i+1, cur_weight + weight[i], list(cur_result))
  
if __name__ == '__main__':
    # 0-1背包问题
    weight = [2, 2, 4, 6, 3]
    n = 5
    max_allowed_weight = 9
    cur_max_weight = -1
    p = ZeroOneBag(weight, n, max_allowed_weight, cur_max_weight)
    p.backtracking(0, 0, [])

总共有19种结果:


image.png

实际上满足要求的只有5种解法:


image.png

2. 备忘录优化重复计算

因为这个求解过程是一个递归,所以我们可以画出其对应的递归树,从而帮助我们理解。


递归树

递归树的每个节点表示一个状态。可以用f(i, cur_weight)表示,i表示放入或不放入第i个物品后,背包当前的总重量。
从图中我们可以看出,存在着较多的没有必要的重复计算。比如,对于f(2, 2)来说,从结果上看,我们并不关心经过前2个物品的判定后结果为2的具体组合是什么。

我们可以使用备忘录的方式避免重复计算。代码如下:



# 0-1背包问题
weight = [2, 2, 4, 6, 3]
n = 5
max_allowed_weight = 9
cur_max_weight = -1

class ZeroOneBag(object):
    def __init__(self, weight, n, max_allowed_weight, cur_max_weight, flag):
        self.weight = weight
        self.n = n
        self.max_allowed_weight = max_allowed_weight
        self.cur_max_weight = cur_max_weight
        self.flag = flag
    
    def backtracking(self, i, cur_weight, cur_result):
        """
        # 将物品一个一个的做判定,当判定到第i个物品当前背包重量为cur_weight时候的情况
        # 初始调用backtrack(0,0)
        """
        # print(i, cur_weight)
        if i == n or cur_weight == self.max_allowed_weight:
            if cur_weight > self.cur_max_weight:
                self.cur_max_weight = cur_weight
            if sum(cur_result) == self.cur_max_weight:
                print('cur_result, cur_max_weight', cur_result, self.cur_max_weight)
            return

        if self.flag[i][cur_weight]:
            return

        # 不加入第i个物品
        cur_result.append(0)
        self.backtracking(i+1, cur_weight, list(cur_result))
        # 若背包重量允许,则加入第i个物品
        if cur_weight + self.weight[i] <= self.max_allowed_weight:
            cur_result.pop(-1)
            cur_result.append(weight[i])
            self.backtracking(i+1, cur_weight + weight[i], list(cur_result))

        self.flag[i][cur_weight] = True
        
       
         




if __name__ == '__main__':
    # 0-1背包问题
    weight = [2, 2, 4, 6, 3]
    n = 5
    max_allowed_weight = 9
    cur_max_weight = -1
    #备忘录, n行max_allowed_weight+1列
    flag = [[False]*(max_allowed_weight+1)]*n
    p = ZeroOneBag(weight, n, max_allowed_weight, cur_max_weight, flag)
    p.backtracking(0, 0, [])
    print(p.cur_max_weight,)



使用备忘录,将原本需要计算的45个状态,缩减到23个,效率大幅度提高。

3. 动态规划的思路

使用备忘录的方式,从执行效率上来讲,和动态规划基本没有差异了。但是动态规划是一种新的思考问题的思路。我们一起来看下。
假设物品有n个,我们把问题的求解过程拆分为n个阶段,每个阶段决策一个物品是否放入背包,每次决策(物品放或不放入背包)过后,背包中的物品会出现不同的结果(状态)。每种状态对应递归树中的一个节点,我们去除重复的节点后,每一次决策会得到一个状态结果集合,每次决策就是从一个集合到另外的一个状态集合。

我们用数组[n][max_allowed_weight+1]表示每一次决策后的状态,每一次决策后的状态数不超过max_allowed_weight+1种。



# 0-1背包问题
weight = [2, 2, 4, 6, 3]
n = 5
max_allowed_weight = 9
cur_max_weight = -1

class ZeroOneBag(object):
    def __init__(self, weight, n, max_allowed_weight, cur_max_weight, flag):
        self.weight = weight
        self.n = n
        self.max_allowed_weight = max_allowed_weight
        self.cur_max_weight = cur_max_weight
        self.flag = flag
    
    def dp(self):
        """
        动态规划的解法
        """
        self.flag[0][0] = True
        if self.weight[0] <= self.max_allowed_weight:
            self.flag[0][self.weight[0]] = True
        for i in range(1, self.n):
            for j in range(self.max_allowed_weight+1):
                #针对上一次决策的结果进行下一轮的决策:不放物品或者放物品
                if self.flag[i-1][j]:
                    self.flag[i][j] = True
                    if j+self.weight[i] <= self.max_allowed_weight:
                        self.flag[i][j+self.weight[i]] = True
        for i in range(self.max_allowed_weight, -1, -1):
            if self.flag[n-1][i]:
                print(i)
                return i

if __name__ == '__main__':
    # 0-1背包问题
    weight = [2, 2, 4, 6, 3]
    n = 5
    max_allowed_weight = 9
    cur_max_weight = -1
    flag = [[False]*(max_allowed_weight+1)]*n
    p = ZeroOneBag(weight, n, max_allowed_weight, cur_max_weight, flag)
    p.dp()

实际上,这里面的二维数组还可以进一步优化,只用一个一维数组即可。



# 0-1背包问题
weight = [2, 2, 4, 6, 3]
n = 5
max_allowed_weight = 9
cur_max_weight = -1

class ZeroOneBag(object):
    def __init__(self, weight, n, max_allowed_weight, cur_max_weight, flag):
        self.weight = weight
        self.n = n
        self.max_allowed_weight = max_allowed_weight
        self.cur_max_weight = cur_max_weight
        self.flag = flag
    
    def dp(self):
        """
        动态规划的解法
        """
        self.flag[0] = True
        if self.weight[0] <= self.max_allowed_weight:
            self.flag[self.weight[0]] = True
        for i in range(1, self.n):
            for j in range(self.max_allowed_weight+1):
                if self.flag[j]:
                    if j+self.weight[i] <= self.max_allowed_weight:
                        self.flag[j+self.weight[i]] = True
        for i in range(self.max_allowed_weight, -1, -1):
            if self.flag[i]:
                print(i)
                return i

if __name__ == '__main__':
    # 0-1背包问题
    weight = [2, 2, 4, 6, 3]
    n = 5
    max_allowed_weight = 9
    cur_max_weight = -1
    flag = [False]*(max_allowed_weight+1)
    p = ZeroOneBag(weight, n, max_allowed_weight, cur_max_weight, flag)
    p.dp()

当然,这里可以从大到小进行判断,可以节省一些赋值计算。

相关文章

  • 动态规划入门总结(未完待续)

    首先通过求解0-1背包问题的思路演化来引出动态规划的方法。0-1背包假设我们有一个最大负重量为9的背包,同时我们有...

  • 动态规划入门总结

    确定状态: 研究最优策略的最后一步; 化为子问题; 状态转移方程: 根据子问题定义直接得到; 初始化条件和边界情况...

  • 4. 动态规划算法

    1. 动态规划算法总结2. 漫画:什么是动态规划?3.算法之动态规划4. 动态规划-算法

  • 这篇将动态规划的文章不错,大家可以看一下

    教你彻底学会动态规划——入门篇

  • 动态规划 Dynamic Programming

    从运筹学和算法的角度综合介绍动态规划 算法分类总结动态规划与静态规划的关系浅析静态规划和动态规划动态规划解非线性规...

  • 动态规划学习总结1 动态规划入门理解

    1.动态规划的本质: 递归 2.原问题(N) - >子问题(N-1)->原问题(N) 3.最优子结构: ​ ...

  • 动态规划入门

    动态规划入门 动态规划(Dynamic programming, 简称DP), 通过把原问题分解为相对简单的子问题...

  • 动态规划--未完待续

    动态规划(Dynamic programming) 动态规划,通过把原问题分解为相对简单的子问题的方式求解复杂的问...

  • 算法与数据结构网址备忘

    kd-tree算法原理与开源代码实现 详解kd-tree 动态规划入门篇 动态规划进阶篇

  • 动态规划

    链接:很特别的一个动态规划入门教程动态规划与贪心算法的区别与联系 那么遇到问题如何用动态规划去解决呢?根据上面的分...

网友评论

      本文标题:动态规划入门总结(未完待续)

      本文链接:https://www.haomeiwen.com/subject/vtumlktx.html