Skip to content

Latest commit

 

History

History
198 lines (145 loc) · 6.06 KB

8.1 递推关系的应用.md

File metadata and controls

198 lines (145 loc) · 6.06 KB

8.1 递推关系的应用

兔子和斐波那契数列

简单来说就是一个僵尸,初始等级为1,每过一个月提升1级,升级到2级之后就可以感染下一个人,将其变成1个1级的僵尸。(我不喜欢兔子的那个例子,因为我总是在纠结一个公兔和多个母兔子的问题)。

然后用递归来计算上面的问题就是:

function getNumber($month){
    if($month==1){
        return [1=>1,2=>0];
    }
    $lastMonth=getNumber($month-1);
    return [1=>$lastMonth[2],2=>($lastMonth[2]+$lastMonth[1])];
}

$result=getNumber(6);

print_r($result);
/**
Array
(
    [1] => 3
    [2] => 5
)
**/

汉诺塔

这个资料很多了,我是参考了B站的这个视频

这里仅仅计算完成汉诺塔需要多少步,并不涉及其中的具体步骤:

<?php

class Town{
    public function totoalStepsNumber($dishNumber){
        if($dishNumber==1){
            return 1;
        }
        return 1+2*$this->totoalStepsNumber($dishNumber-1);
    }
}

$town=new Town();
print $town->totoalStepsNumber(4);

这里的思路很简单,就像宋丹丹和赵本山的那个小品一样,把大象关进冰箱需要几步,3步:打开冰箱门,把大象放进冰箱,关上冰箱门。

这里是同样的意义,无论中间的具体操作是什么,移动第i个汉诺塔的盘子,都需要先将前面i-1个盘子移动到另一个盘子上,然后移动这第i个汉诺塔盘子,然后再将前面第i-1个盘子移动到这个盘子上。

不包含连续两个0的字符串

故名思义,就是在长度为n的字符串中,所有不包含连续两个0的字符串总量有多少?

这里我看错书上的意思,结果写成了获取所有的可能值的代码,但是结合这个看,可以更好理解总量的部分:

<?php

function withoutDoubleZero($length){
    if($length==2){
        return ['01','11','10'];
    }
    $lastword=withoutDoubleZero($length-1);
    $returnData=[];
    foreach($lastword as $word){
        if(substr($word,-1,1)=='0'){
            $returnData[]=$word."1";
        }else{
            $returnData[]=$word."0";
            $returnData[]=$word."1";
        }
    }
    return $returnData;
}

print_r(withoutDoubleZero(5));

或者我换成下面的图可以更容易理解:

在0的后面只能跟1,在1的后面可以随便跟0和1,都不会违反出现连续两个0的的情况。跟最开始的斐波那契数列的情况是一样的。

动态规划

参考B站视频

那个参考视频讲的真的很好,如果可以,建议直接去看视频。

这里首先定义一下相容的概念:讲座i的开始时间大于等于讲座k的结束时间,那么就可以说这两个讲座是相容的,在上面的例子中,讲座1和讲座2是不相容的,讲座3和讲座1是相容的。

再来就是p(j)的定义,其表示为对于讲座j来说接近的相容讲座,比如对于讲座7来说,讲座1,2,3,4都相容,但是讲座3最接近,所以p(7)=4

  • p(1)=0
  • p(2)=0
  • p(3)=1
  • p(4)=0
  • p(5)=0
  • p(6)=2
  • p(7)=4

接下来加入每场讲座能参与的人员数:

接下来加入一个函数:opt(n),它代表按照时间往前排序时,安排第n场讲座的最优解。

现在从讲座7开始考虑,对于讲座7有2种选择,安不安排讲座7:

  • 安排讲座7:总人数为opt(4)+10

  • 不安排讲座7时:总人数为opt(6)

这里解释两个值,为什么安排讲座时,总人数是opt(4)+10opt(4)是因为如果一定要安排讲座7,则不可能安排讲座5,6,因为5,6和7不兼容,所以只能是和7兼容的最大值,即opt(p(7)),再加上讲座7所能容纳的参与人数10。

再来就是不安排讲座7时,那么就先假设总人数最优解为opt(6),这里不用纠结为什么是opt(6),因为opt(6)也不一定是我们的最终结果,这里只是将其作为一个中间值而已。

按照上面的思路,我们继续考虑,不安排讲座7时,opt(6)的值,这个时候又有2个选择,是否安排讲座6:

  • 安排讲座6:总人数为opt(2)+20
  • 不安排讲座6:总人数为opt(5)

将上面的结果制作成树状图就是下面的形式:

从最上面开始看起,如果opt(7)可以延生出两条线,opt(4)+10opt(6),分表代表安排讲座7和不安排讲座7,接下来针对opt(4),也有两种选择,安排讲座4和不安排讲座4,安排讲座4则能增加30人,不安排讲座4,则计算opt(3)。以此类推直到opt(1),如果安排讲座1,则人数为10人,不安排讲座1,则0人,所以我们可以知道opt(1)=10,然后往上走到opt(2),如果安排讲座2,则人数为20人,否则人数为opt(1)=10人,所以opt(2)=20,以此类推直到回到最上面的opt(4)+10=40

接下来在opt(6)那一侧,因为之前就计算过opt(2),opt(4)的值,所以不会再去计算一次,按照左侧的计算逻辑,得出最优解为只安排讲座5,人数为100人。

<?php

$p=[
    1=>0,
    2=>0,
    3=>1,
    4=>0,
    5=>0,
    6=>2,
    7=>4
];

$sites=[
    1=>10,
    2=>20,
    3=>20,
    4=>30,
    5=>100,
    6=>20,
    7=>10
];
$cache=[];

function opt($n){
    global $sites,$p,$cache;
    if(isset($cache[$n])){
        return $cache[$n];
    }
    if($n<=0){
        return 0;
    }
    $cache[$n]=max(opt($n-1),opt($p[$n])+$sites[$n]);
    return $cache[$n];
}

print opt(7);
print PHP_EOL;
print_r($cache);

输出结果:

100
Array
(
    [1] => 10
    [2] => 20
    [3] => 30
    [4] => 30
    [5] => 100
    [6] => 100
    [7] => 100
)