写给媳妇儿的算法(十四)——动态规划

【引言】有一个准备偷东西的小偷,他有一个用来偷东西的背包,这个背包只能装4斤重的东西。但是他所要偷的店里,却有几十种上百种的商品,这些商品质量价格不一,那么他该如何选择才能偷到最好的一个物品集合使得他所偷取的东西总价最高呢?

动态规划也是一种能将问题的规模缩小,解决了小问题,逐渐的扩大问题规模就最终得到了问题的解答的这么一个算法。

算法过程

背包问题

如引言里所说,小偷偷东西的这种问题,就是背包问题。背包问题需要我们选择出最好的一个物品组合来实现小偷偷东西的利益最大化。

问题规模

实际上,我们仔细思考这个问题的时候,可能脑海里第一个出来的方案就是把所有能偷的组合都列举出来,我们选择其中背包能装下的并且所偷物品价值最高的一个不就得了。但是,这样做的代价是很高的。

对于小偷所要想偷的东西,有N种,这N种就是一个可供选择的集合,小偷需要从中选择一个子集来偷,含有N个元素的集合的子集的个数是:

含有N个元素的集合的子集个数.png

这也就是说,随着商品个数的增加,小偷需要考虑的偷取商品的几何数量会指数级别的增长,如果有 100 种商品,那么可以偷的情况就有 1267650600228229401496703205376 种,小偷要想从这些中情况中选择满足背包重量要求的其中一个组合,根本就无法列举并抉择。

动态规划

我们跟着动态规划算法的过程来看一下,动态规划是怎么“动态”的规划小偷该偷哪几种东西。简单一点,我们现在的问题变为,小偷的背包只能装4斤重的东西,他可以偷的东西只有三种分别是:


小偷可以偷的东西列表.png

此时背包容量只有4,要想拿走所有的东西,背包是装不下的,所以要有所取舍。我们使用动态规划来进行操作。所有商品的质量(为了简便)都是整数,背包容量是4,我们就用所有商品质量中的最小的分割单位(1斤)来对背包的容量进行分割,形成一个以最小单位为列(背包容量是最大值),每件商品为行的一个表格:


建立分别以质量最小划分单位和商品信息为横纵坐标的表格.png

接下来,我们开始填充表格,在整个填充过程完成之后,我们就能得到最终的选择结果了。

我们从上至下,从左至右的填充整个表格。首先来看第一排,第一排的意思是,小偷此时 只能选择偷取Bose耳机 ,小偷背包的最大容量是4,我们拆分开来,假如我们分别拿出1、2、3、4(最大)的容量来偷取Bose耳机,我们把每种容量偷取Bose耳机所能获取的偷取商品的最大价值填入表格。

由于Bose耳机的质量是1,所以,当背包容量是1的时候,小偷是可以偷取Bose耳机的,所以第一个格子我们填入Bose的价格3000:


第一个格子填充完毕.png

继续向后填写。第二个格子,此时背包容量是2,但是小偷现在只能偷取Bose耳机,尽管Bose耳机的质量是1不能够填满背包。所以第二个格子还是只能装一个Bose耳机,同理背包容量是3、4此时在第一行都只能填入一个Bose耳机,于是我们就将表格的第一排都填充完毕了:


第一排填充完毕.png

此时,图表告诉我们的是,如果我只能偷取Bose耳机这一种东西的话,当背包容量为4的时候,我最多能偷取的物品的价值就是3000(一个Bose耳机)

继续向下走,我们开始填充第二排,第二排代表的是,此时,小偷不仅能偷取Bose耳机,还能够偷取iPhone,而且小偷要优先于iPone偷取

首先是第二排的第一个位置,此时背包容量是1。我们通过第一排知道,目前为止,当背包容量为1的时候,小偷所能偷取的最大的价值是3000。此时,因为iPhone的质量是3,装不下,所以此时所能偷取的物品的最大价值还是3000:

第二行第一个格子填充完毕.png

同理,第二个格子所能偷取的最大的价值也是3000。

此时来到了第三个格子,这个格子代表此时背包的容量为3,正好可以装下iPhone。如果小偷此时装下iPhone,那么这个格子的价值将是8000,比之前(上一行容量3的位置)的3000要高,所以小偷此时偷取的物品变为iPhone,价值变为8000:


所能偷取的最大价值发生变化.png

到了第二排第四个格子,此时背包容量为4,小偷可以先装入iPhone,装入了iPhone之后,背包容量被占用了3,还剩下1。此时小偷偷取了iPhone之后剩下的这1的容量还能装入的最大价值的物品是哪些呢?这个答案要从第一排来找,因为第一排是除了iPhone之外剩下小偷所能偷的所有的商品的所对应的不同容量下的最大价值。这时我们找到了,当容量为1(第二排的4容量装了3容量的iPhone之后所剩下的)时,小偷能装入背包的最大的价值就是第一排的第一个格子所记录的3000!:


第二排最后一个格子.png

此时,当小偷手中只有一个容量为4的背包时,所能偷取的最大的物品的价值是11000。

最后一排。来到最后一排,这时,小偷能偷取的物品又多了一个iPad

最后一排第一个格子,当背包容量为1的时候。由于iPad的质量为4装不下,所以,我们还是看它上一排,除了iPad之外所能装在容量为1的背包中的最大的价值,是3000,同理最后一排第二个跟第三个格子也是如此:


第四排前三个格子填充完毕.png

我们来到了最后一排的最后一个格子。此时背包容量为4,iPad的质量也为4,iPad的价格是10000。当小偷背包中不放iPad的时候(上一行的容量4的位置),容量为4的背包所能装的最大的商品价值是上一排代表容量为4的格子所填写的11000。11000 > 10000。也就是说如果背包此时不装iPad,改装除了iPad之外其他的东西,所能装的最大的价值要比装iPad要高。那么小偷就不装iPad,继续延续之前的选择的物品:


表格填充完毕.png

至此,表格填充完毕。最后一个格子所代表的含义就是,当小偷可以选择的商品有 Bose耳机、iPhone、iPad与此同时背包容量为4的情况下,所能偷取的商品是Bose耳机、iPhone,总价值是11000。

回顾一下最终的做出选择的流程,我们总结一下规律:


整体流程.png

对于每个格子,填入格子中的值不外乎是这两种中的一种:


格子中的值有两种情况.png

既然保证利益最大化,我们就取这两种赋值方式中能得到的值较大的那个。这样,我们整体的动态规划的流程就结束了。

动态规划是将整个一个大问题零敲碎打,分解成为背包容量分别为1、2、3、4时候,物品分别为:Bose耳机;Bose耳机、iPhone;Bose耳机、iPhone、iPad这样依次增加商品的情况下最终通过解决每个小格子,来使最终的这个大的格子所代表的问题得到解决。这样做的好处在于如果我们此时再增加一个商品,无非就是再多出一行来计算,最终也可以得到正确的结果!回顾一开始的假设,加入有100种商品,动态规划所需要的计算步骤需要 4 × 100 = 400 < 1267650600228229401496703205376 ,非常可观。

算法实现

# -*- coding: utf-8 -*-
# @Time    : 2019-03-19 09:17
# @Author  : Jaedong
# @Version : 3.6
# @File    : DynamicProgramming.py
# @Software: PyCharm

class Goods:
    name = ''
    price = 0
    weight = 0

    def __init__(self, name, price, weight):
        self.name = name
        self.price = price
        self.weight = weight


def dynamic_programming(goods, min, max, step):
    # 初始化分部计算的中间过程
    steps = [[0 for col in range(min, max + 1, step)] for raw in range(len(goods))]
    choosed = []
    # 每一行的每一个格子(分开的步骤)
    for i in range(len(goods)):
        # 每一列的每一个格子(分开的步骤)
        for j in range(min - step, max, step):
            weight = goods[i].weight # 拿到当前行的物品的重量
            sameWeightMax = 0 # 初始化相同重量(同一列)上面已知的不包括本物品(本行物品)的最大的重量所装物品的价值
            currentStepPrice = goods[i].price # 当前一行不同重量所能装的最多物品的价值

            # 为了防止越界,第一行要特殊处理,非第一行的才走下面的赋值逻辑,其实也可以用哨兵
            if i != 0:
                sameWeightMax = steps[i - 1][j]

                if j >= weight:
                    currentStepPrice = steps[i - 1][j - weight] + currentStepPrice

            # 如果商品的重量小于等于背包的可以承受的重量,那么就可以考虑是否应该装进去, j+step是当前步骤的重量
            if weight <= j + step:
                if sameWeightMax > currentStepPrice:
                    steps[i][j] = sameWeightMax
                else:
                    steps[i][j] = currentStepPrice
            else:
                steps[i][j] = sameWeightMax

    # 回溯一下,找到所包含的物品都有什么
    choosed = []
    colIndex = max - 1 # 减1之后代表的是下标,不减1代表重量
    for i in range(len(goods) - 1, -1, -1):
        good = goods[i]
        if steps[i][colIndex] > steps[i-1][colIndex]:
            choosed.append(goods[i].name)
            colIndex = colIndex - good.weight

    if colIndex >= 0: # 如果最终还有重量在的话(真实重量应该+1),这里避免了首个商品恰好是漏下没有记录的情况
        choosed.append(goods[0].name)

    return choosed


goods = [
    Goods('Bose耳机', 3000, 1),
    Goods('iPhone', 8000, 3),
    Goods('MacBook Pro', 10000, 4)
]

# 这个示例是以重量为整数单位,如果涉及到小数,可以适当的进行改版
choosed = dynamic_programming(goods, 1, 4, 1)
print('最终选择的商品是:{}'.format(choosed))

结果是:

算法的最终结果.png



上一篇:写给媳妇儿的算法(十三)——贝尔曼-福德算法
下一篇:写给媳妇儿的算法(十五)——二叉树及其遍历

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

推荐阅读更多精彩内容

  • 2017年9月12日,苹果发布了新一代的 iPhone 8 和 iPhone X,iPhone 手机已在不知不觉中...
    40c0490e5268阅读 3,549评论 6 34
  • 岁月长河水东流, 终年不改情悠悠。 遥望千里难相见, 一任情愫何时休? (清风明月于元月二十六号)
    清风明月冯耀杰阅读 177评论 3 22
  • 一部遇见爱情的利先生 让的我眼泪止不住的往下流 真情的流露 爱情里的情非得已 爱情里的无能为力 竟是这样的无力 我...
    路上过客阅读 266评论 28 11
  • 1.引言 前面把代码中函数的重构记录了下,今天在记录下在对象之间搬移特性。 2.正题 《重构改善既有代码的设计》中...
    过期的薯条阅读 1,169评论 0 1
  • 狂风呼啸黄沙飞, 抛妻别子无怨悔。 男儿纵横思报国, 尽扫边寇立国威。 八万壮士昭日月, 一颗丹心映边陲。 快意岂...
    成都独行侠阅读 260评论 1 4