代码改变世界

数羊问题

2015-08-06 12:31  迷思的猫  阅读(727)  评论(1编辑  收藏  举报

有一只羊,它的寿命是5年,在第二年和第四年能够产下小羊。请问第N年时,羊群里有多少只羊?

题目就这么简单,还有一些附加说明:

1.羊的寿命可以理解为0-4或者1-5,反正就是它能活5x365天,第一次产羊就是1岁(0-4)或2岁(1-5)时。

2.伟大的灵魂都是雌雄同体的,羊生羊不需要其他羊的帮助。

3.别跟我说心算……当N等于几百万的时候,还能心算吗?

 

我把这个问题抽象出来:一只羊在有限的生命力,能够裂变成两只,那么长期来看,羊群的数量应该是指数增长的。这其中一定有一个公式,里面会有乘方次方之类的东西,或者是对数一类的东西,羊群的数量线画出来,就是一个形似 y=x^2 之类的线。

然而我的脑洞并没有那么大,没法脑补出这个公式,于是我开始写代码:把羊视为一个对象,这个对象在合适的年份会产下小对象,外面套一个for循环,循环年份,即可得到i年份的羊群,然后数一下羊群即可。

<?php
class Sheep{
    public $birth;
    public $death;
}

$sheep = new Sheep;
$sheep->birth = 0;
$sheep->death = 5;
$sheeps = [
    $sheep
];
for($i=0; $i < 10; $i++) { 
    foreach($sheeps as $k=>$sheep){
        if($i-$sheep->birth==1 || $i-$sheep->birth==3){
            $child = new Sheep;
            $child->birth = $i;
            $child->death = $i+5;
            array_push($sheeps, $child);
        }
        if($sheep->death == $i){
            unset($sheeps[$k]);
        }
    }
}
echo count($sheeps);

结果是当i接近40的时候,我的程序就崩掉了,原因是PHP使用了超过128M的内存。

不论我们把羊对象换成数组,还是抽象成只有出生年份的数组元素,都无法解决这个问题,因为羊的数量是(近似于)指数增长,内存必然是永远不够用的。

 

但是不要紧,我认为这个公式本身应当是很简单的,那我们可以用MATLAB去拟合目前计算的结果,是不是能把公式蒙出来。手头没有MATLAB,那就用walframalpha吧。

这个结果显然是不对的,我又丢进去了更多的参考数,结果也还是不对的——此路不通。

 

在我刚验证出这个结果的时候,一个朋友给出了答案,他画了这么一张表:

横轴的0-11是“羊年纪元”,纵轴是羊年龄,中间的数字是:在这一年时,羊群里X岁的羊,有多少只。

斜着去看能看到一排1,一排2,3,5,8——斐波那契数列。如果你觉得不够明显,我们假设羊不会死:

这样足够明显了,在偶数年时,羊数量等于 Fibonacci(YEAR/2+1)。解释下,Fibonacci(n)=1+1+2+3+5+...(一共加n个数)。奇数年时,羊数量等于 SheepCount(YEAR-1)。当然,这是基于羊不会死的假设。

羊的寿命只有五年,所以要去掉斐波那契数列中一些“前面”的数字。公式变为:

偶数年:Fibonacci(YEAR/2+1) - Fibonacci(YEAR/2-2)

奇数年:Fibonacci((YEAR-1)/2+1) - Fibonacci((YEAR-1)/2-1)

最终代码(因为公式有点“错位”,所以某些部分有一丢丢的增减):

<?php
function fib_recursive($n){  
    if($n==1||$n==2){
        return 1;
    }else{
        return fib_recursive($n-1)+fib_recursive($n-2);  
    }  
}  
function count_sheep($y){
    $y = $y + 2;
    if($y % 2 == 1){
        return fib_recursive(($y-1) / 2 + 2) - fib_recursive(($y-1) / 2);
    }else{
        return fib_recursive($y / 2 + 2) - fib_recursive($y / 2 - 1);
    }
}
for($i = 1; $i < 500; $i++){ 
    echo $i.",".count_sheep($i)."\r\n";
}

最后,使用PHP计算,在算到60年的时候,每一次计算已经慢成龟,后面每一次都更慢更慢,果然复杂度还是无法避免。。。