一,原问题,经典兔子繁殖问题。
数学家Leonardoda Fibonacci提出的兔子繁殖问题:
有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少对?
由此推出了著名的fibonacci数列。
递推公式为,F(n)=F(n-1)+F(n-2),n>=3。F(1)=F(2)=1;
还有通项公式,以及黄金分割等的讨论,请参考其他资料。
一句话理解此递推公式:
F(n)是当前月的兔子对数目,等于上个月已有的兔子对数(F(n-1)个),再加上新出生的兔子对数目(F(n-2))。上上个月的兔子对是F(n-2),每对兔子生一对新的兔子,所以新出生的兔子对为F(n-2)。
根据此公式,可以用循环、递归和dp三种方法来实现兔子的计数。
程序在最后给出,本文先将注意力放在问题改进上。
二,去掉“兔子不死”的假设。
我们很容易i注意到原兔子繁殖问题中有个很强的假设,就是“所有兔子都不死”,而实际的兔子是有寿命的,所以修订模型,假设兔子出生后第5个月就死去。
几句话理解修订后的兔子繁殖问题:
兔子分三类:新兔子不繁殖;老兔子会死掉;壮年兔子才有繁殖能力。
月龄分布:[0,2)月;[5,INF)月;[2,5)月。
先给出新问题的递推公式:
F(n) = F(n-1)+F(n-2)-F(n-4);n>=5
F(1)=1;F(2)=2;F(3)=3;F(4)=3;
推导过程请看下图:
图片第一行数字为“月份数”,从1月到16月。
第二行为经典兔子繁殖问题中,每个月的兔子总数。
图片最后两行数字,为经典问题和修订问题在每月兔子数目上面的对比,修订后的问题,去掉了“兔子永远不死”这个条件,每个月的兔子数目都比原问题中要少,月份越靠后,差距越大。
中间部分采用英文字母来给兔子编号,是我的主要推导过程,行与行,列与列都对齐了。
第一对兔子叫a,3月时生了b兔子,4月时生了c兔子,5月就死了。对应的地方有个X号,表示a兔子死去。a兔子对退出历史舞台的那个月,b兔子刚有繁殖能力,生了d兔子,cc兔子对还没有繁殖能力。
后面的兔子出生,兔子具备繁殖能力,兔子老死,依次类推。
延伸一下,修订后的兔子问题,更接近现实中的情况,就像人口演变的莱斯利模型。如果人类不死的话,莱斯利模型里面的人口增长趋近于fibnoacci数列。
三,java程序实现。
1,循环实现。
public static void main(String[] args){
int f1 = 1;int f2 = 1;
int n = 16;
System.out.print("第"+n+"月的");
while(--n>=2){
int tmp = f2;
f2 = f1+f2;
f1 = tmp;
}//while
System.out.println("兔子数目为"+f2);
return;
}//main
2,递归实现。
public static void main(String[] args){
int n = 16;
System.out.print("第"+n+"月的兔子数目为"+fb(n));
return;
}//main
private static int fb(int n){
if(n==1||n==2)return 1;
return fb(n-1)+fb(n-2);
}//fb
3,dp实现。
public static void main(String[] args){
int n = 16;
int[] nums = new int[n+1];
nums[1] = nums[2] = 1;
for(int i = 3;i<=n;i++){
nums[i] = nums[i-1] + nums[i-2];
}//for i
System.out.print("第"+n+"月的兔子数目为"+nums[n]);
return;
}//main
4,修订问题的dp解法
增加第5个月后会死。
package cn.edu.dodi.test;
public class Lianxi {
public static void main(String[] args){
int n = 16;
int[] nums = new int[n+1];
nums[1] = 1;nums[2] = 2;nums[3]=3;nums[4]=3;
for(int i = 5;i<=n;i++){
nums[i] = nums[i-1] + nums[i-2] - nums[i-4];
}//for i
System.out.print("第"+n+"月的兔子数目为"+nums[10]);
return;
}//main
}