数据结构与算法-动态规划(1)

先来讲个故事:
从前有个皇帝,某一天在他的国境内发现了10座金矿,这10座金矿大小不同,开采需要的人力和能挖出来的金子各有不同。考虑到民力有限,皇帝只能征调10000民夫开采这10座金矿,于是问题来了:10座金矿全部去挖,民夫肯定不够,如何选择呢?
皇帝来到10号金矿视察,此时探子来报:
“启禀陛下,这座金矿需要1500人开采,能挖出8500金字”。
皇帝听到报告,突然灵机一动,叫来了手下的左右两个丞相,
“两位爱卿,朕有一册,可知如何挖到最多的金子。左丞相,你去探查一下,给你8500民夫去挖1~9号金矿,你最多能挖出多少金子。右丞相,你也去探查一下,如果给你10000民夫,去挖1~9号金矿,你最多能挖出多少金子”
左丞相答到:“陛下的意思,如果我用8500民夫去挖1~9号金矿,挖出来的金子加上陛下在10号金矿挖的8500金,比右丞相用的10000民夫挖1~9号金矿挖出的金子要多,陛下就挖10号金矿,是否?”
右丞相答到:“如果这10号金矿的8500金加上左丞相用8500人挖1~9号金矿的金子还不及我用10000民夫挖1~9号金矿的金子多,陛下就不挖10号金矿,是否?”
“嗯~~~两位爱卿果然聪慧,甚得朕心!”皇帝拍拍屁股回宫,等两个丞相的报告。
皇帝陛下一走,两个丞相就跑到9号金矿去了,此时探子来报:“启禀两位大人,这9号金矿需要1000民夫,能挖出7000金”,两位丞相不慌不忙,叫来了自己的手下。
摆在左丞相面前的问题,8500人,去挖1~9号金矿,最多能挖出多少金子?左丞相依样画葫芦,给手下的两个尚书派了任务:“左丞左尚书,你去查一下,给你7500人,挖1~8号金矿,最多能挖出多少金子?左丞右尚书,你去查一下,给你8500人,去挖1~8号金矿,最多能挖出多少金子?”
左丞相手下的两个尚书也不是等闲之辈,立马领会的领导的意图,
“大人的意思是,如果我用7500人挖1~8号金矿加上大人在9号金矿挖的7000金,比右尚书用8500人挖1~8号金矿挖出的金子要多,大人就挖这9号金矿,是否?”左丞左尚书答到。
“如果左丞右尚书用7500人挖1~8号金矿加上大人在9号金矿挖的7000金比我用8500人挖1~8号金矿要少,那大人就不挖这9号金矿,是否?”左丞右尚书答到。
“嗯~~你二人果然是可造之才!”左丞相拍拍屁股回府,等两位尚书的报告去了

故事讲到这里,各位看官是不是已经琢磨出一点味道了?这就牵扯出了动态规划的第一个要点:

  • 子问题:

    皇帝的决策是基于两个丞相的答案以及10号金矿的信息才能判断出最多能够开采出多少金子。而丞相解决的问题,就是皇帝要解决问题的子问题。不管是皇帝、丞相,还是尚书,他们要解决的问题本质上是一样的!给你一点人,几座矿,你能挖多少金子?他们每个人的差别仅仅是人和矿的数量。这其实体现了动态规划的一个基本思路:

    把母问题拆分成逻辑一样,规模变小的子问题,通过子问题的结果,决策出母问题的答案!而拆分问题的方法,被称为转移方程

    子问题具有以下特点:

    – 最优子结构性质
    皇帝要做出正确的决定,前提是丞相给他的必需是最正确的答案,这种子问题最优时,母问题通过优化选择后一定最优的情况叫做“最优子结构”。

    – 子问题独立性质
    左丞相不会关心右丞相怎么解决,左尚书不会关心右尚书怎么算,实际上同一层级的两个子问题互相之间不会影响对方,这种一个母问题在对子问题选择时,当前被选择的子问题两两互不影响的情况叫做“子问题独立性”

  • 边界
    想象一下1号金矿的场景,一个底层县令跑到1号金矿,此时他面对的问题非常简单,我手上的人够不够挖这个矿?人够,他的问题答案就是这个矿能挖出的金子数量。如果不够,答案就是0。这种不再能拆分的子问题,就是整个动态规划的边界。

设金矿的编号为:mineN
第N个金矿能够挖出的金子数:gold[mineN]
第N个金矿需要的民夫数:people[mineN]
f(people,mineN)表示当有people个人在编号为0~mineN的金矿时能够得到的最大金子数

mineN = 0; people >= people[mineN]; f(people,mineN) = gold[mineN]
mineN = 0;people < people[mineN]; f(people,mineNum) = 0
mineNum != 0; f(people,mineN) = max( f(people-people[mineN], mineN-1) + gold[mineN], f(people, mineN-1) )

import UIKit

class ViewController: UIViewController {

    let mineN:Int = 9;
    let peopleTotal = 10000;

    let gold:[Int] =  [8500,9000,10000,7500,7000,6500,5000,5000,9300,8500];
    let people:[Int] = [6500,9200,7300,7600,8900,6300,7200,9100,8100,7900];

    func getGold(p:Int, m:Int)->Int
    {
        if(m == 0)
        {
            if(p >= people[0])
            {
                return gold[0];
            }
            else
            {
                return 0;
            }
        }
        else
        {
            let l = getGold(p: p - people[m], m: m-1) + gold[m];
            let r = getGold(p: p, m: m-1);

            return max(a: l, b: r);
        }
    }

    func max(a:Int, b:Int)->Int
    {
        if(a > b)
        {
            return a;
        }
        else
        {
            return b;
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.


    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    @IBAction func onClick(_ sender: AnyObject) {
        let r = getGold(p: peopleTotal, m: mineN);

        print(r);
    }

}

代码就是这样了,但是还没完,下一篇我们来研究下动态规划的时间复杂度和优化策略

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值