动态规划——算法


零、时间与成果的最佳策略

一个老问题:
有一件物品价值15(W)元,现在有货币价值100元、50元、20元、10元、5元、1元,每张价值的货币张数不限
请问:如何用最小的货币张数,凑出价值W元?

贪婪策略

这样一个老生常谈的问题,根据个人的生活体会,当我们取可以取的最大面额,肯定是能最快的缩短到达W元的张数。这种思考方式又称作贪婪策略。


15元的贪婪策略

可以看到,当W=15时,W=10+5(两张),大家完全可以拍着大腿说,两张,那肯定是最优算法,毕竟一张的情况已经被排除了。如果我们在放大W的值,当W=76时,W=50+20+5+1(四张),可以看到贪婪策略只取当前最优解的做法,非常优秀,极其有利于快速找到一个可接受的解(有些情况下贪婪策略会导致无解,需要另一种思路的支持,鱼之后会说)。
贪婪策略追求的是局部最优解,假设我们加入另一种货币11元,此时当W=15时,W=11+1+1+1+1(五张),这种就叫捡了西瓜丢了一地芝麻,正如他的名字一样,这就是贪婪的真实面目。甚至说有可能发生贪婪到最后解决不了问题的情况发生。不可能所有的问题都能简单的期望单纯的追求局部最优解最后的结果也是最优解。

穷举策略

15元的穷举策略

当没有办法直接找到最优解决方案时,另一种策略诞生了:穷举。即算出所有结果的可能性,然后记下最优解。这种方法思路简单且明确,尤其是计算机发达的今天,如果不追求效率,建个模型扔给电脑,让它慢慢穷举,只要不是过于复杂很快也能得到答案。游鱼突然回想起以前做有些选择题,全部带入求解,正是穷举的表现,凡是可以把选项带入求解的绝对不会错,而且若果有多选,也是不会错过正确答案。穷举给算法垫底了底线,假设你设计的算法效率和穷举一个样,为何不用穷举呢?


大多数情况下,我们不可以容忍非最优解,甚至是有可能无法求解,似乎穷举成为了我们唯一的路。但是穷举的时间复杂度是我们无法忍受的。现在来思考一下为什么贪婪策略的大问题的最优解无法由小问题的局部最优解推导出来?

一、最优子结构

依然是原来的问题:


货币问题

假设我们要凑出的价值W所需要的货币张数是f(w),当我们用贪婪时,我们取的解是f(w)=1+f(4),f(4)=4。贪婪策略自以为是把f(4)定义成了小问题的最优解。很明显在取一张的情况下,我们要考虑的还有如下几种情况:

三个可能的最优解

鱼瞬间发现一个问题:贪婪策略的问题在于当最优子结构不定时,因为贪婪策略一味的追求最大的11元,忽视了他的f(4)并不是最优子结构。假设对于任意的W都有f(w)=n+f(m),当n为定值,最小的m对应最小的f(m),那么贪婪策略瞬间成为最优策略。瞬间一个新问题出现了:当前的货币政策是否符合贪婪策略下即为最优解?如果不符合那么如何设计货币政策呢?
此时最小的f(m)就成为了最优子结构,而n为定值,所以f(w)这个大问题也就迎刃而解了。关键点就在于n为定值,这点又有人称这种现象叫无后继性。

二、重叠子问题

递归和循环是最简单的重叠子问题!重叠子问题的关键点就在f(n)可以由f(n-1)求得,当然可能并不是简单的多项式关系,因为多项式关系已经被前面的递归和循环拦住了,自然不需要高级的求解技巧。
回到最开始的问题,货币回归到100元、50元、20元、10元、5元、1元,解得价值W的最优策略!

#!/usr/bin/C#
//虽然此货币结构已经是贪婪策略下的最优解了,但还是练习一下,毕竟这个算简单的
//这个方法是前面解析子结构而得到的,与网上流行的方法有些不太一样。
using System;

namespace ConsoleApplication4
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("目前的货币政策,请用逗号隔开、统一单位、只要数字");
            string lines = Console.ReadLine();
            string [] line = lines.Split(',');
            int[] money = new int[line.Length];
            Array.Sort(money);
            for (int i = 0;i< line.Length; i++)
            {
                money[i] = int.Parse(line[i]);
            }
            Console.WriteLine("目前需要凑到的金额数");
            int aim = int.Parse(Console.ReadLine());
            int[] dp = new int[aim+1];//用来记录dp下角标所标识的金额最小解。
            int cost;
            for(int m = 1; m <= money[0]; m++) dp[m] = money[money.Length-1];//这边属于无法凑出的金额。所以当dp的值大于等于max(money[])时说明此金额无法凑出。
            for (int i= money[0]; i<= aim;i++)//从最小的可凑金额min(money[])开始计算每个金额的最小解。
            {
                cost = int.MaxValue;
                for(int j = 0;j< money.Length; j++)//这里的思路就是:因为是从最小的可凑金额开始的min(f(m)) = 1+min(f(m-money[])),其中min(f(m-money[]))在前面循环已经求得解或者是无解,那么自然min(f(m))也就无解或者就是min(f(m-money[]))+1
                {
                    if (i >= money[j]) cost = min(cost, dp[i- money[j]] +1);
                }
                dp[i] = cost;
            }
            
//此时dp的下角标就是对应金额的解,这里没高兴输出。
            Console.ReadLine();
        }
        static int min(int a,int b)//取最小值
        {
            return a > b ? b : a;
        }
    }
}

我们来回顾下此处的逻辑:
首先金钱的面额被我们存到数组money[]里面了,其次我们定义dp[i]=0,i=0(dp表示解),当0<i<min(money[])定义无解。然后我们根据f(w)=n+f(m)就可以逐步求解:即f[w]=1+min[f(m-money[?])]。最后就可以得到f(w),w=W的值了。

动态规划入门杰作:背包问题

(这里只介绍下01背包问题,如果大家还有兴趣以下有个网站可以学习思路,更加复杂的思路)
详询动态思考的艺术

经典的背包问题

前面的货币问题让俺们知道了,用动态规划解决问题就是要优化出诸如f(w)=n+f(m)的形式(即找出问题的子结构),然后才是想怎么解得f(m)的最优解。那么如何化解出最优子结构?


最优子结构

记得前面的货币问题吗?鱼来重复一下,就是不是就呼之欲出呢?(其实这就是个不一定要凑满的货币问题只不过衡量标准不同导致构造子问题的方法不同)
初步解法:
等价转化:前i个物品放到体积为v的背包当中=max((前i-1个物品放入体积为v的背包当中),(前i-1个物品放入体积为(v-第i件物品的体积)的背包当中+第i件物品的价值))


情况详解

复习一下前面的:
n个货币凑出价值w = 1 + min(n-1张货币凑出价值w-剔除的那张货币的价值)

鱼只能说,多多接触,一定能摸索出盲凑最优子结构的能力。

#!/usr/bin/C#

            int W =15;
            int[] w = { 5, 4, 7, 2, 6 };
            int[] v = { 5, 3, 10, 3, 7 };
            int[,] dp = new int[v.Length+1,W+1];
            for (int i = 1; i <= v.Length; i++)
            {
                for (int j = 0; j <= W; j++)
                {
                    if (j < w[i-1]) dp[i,j] = dp[i - 1,j];
                    else dp[i,j] = max(dp[i - 1,j], dp[i - 1,j - w[i-1]] + v[i-1]);
                }
            }

优化解法:
第一个方法在估算价值时,纬度涉及到了物品数量和体积,时空复杂度为o(VI),难道不可以像货币问题一样降低复杂度吗?比如只用一维数组记录每个V对应的最优解?
基本可以确认方法肯定是f[v]=max{f[v],f[v-c[i]]+w[i]}

#!/usr/bin/python
w=(5,4,7,2,6)
v=(5,3,10,3,7)
W = 15
dp = [0 for i in range(W+1)]
#初始化空间
for i in range(1,len(w)+1):#取一件物品到取i件物品
    for j in range(len(dp)-1,w[i-1]-1,-1):#对应v的最大价值
#为何此处v要倒序?因为我们求解求的是max((前i-1个物品放入体积为v的背包当中),(前i-1个物品放入体积为(v-第i件物品的体积)的背包当中+第i件物品的价值)),想一下如果不倒序,此时应该是max((前i-1个物品放入体积为v的背包当中),(前i个物品放入体积为(v-第i件物品的体积)的背包当中+第i件物品的价值)),因为因为正序,(v-第i件物品的体积)的实际内容已经不是(前i-1个物品放入体积为(v-第i件物品的体积)的背包当中+第i件物品的价值))了
        dp[j] = max(dp[j], dp[j - w[i-1]] + v[i-1])
print(dp)

如此带入动态规划的思想。前路漫漫

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

推荐阅读更多精彩内容