数羊问题
2015-08-06 12:31 迷思的猫 阅读(748) 评论(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年的时候,每一次计算已经慢成龟,后面每一次都更慢更慢,果然复杂度还是无法避免。。。