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

首先通过求解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()

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,377评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,390评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,967评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,344评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,441评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,492评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,497评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,274评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,732评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,008评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,184评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,837评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,520评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,156评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,407评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,056评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,074评论 2 352