简明现代魔法 -> C/C++ -> 趣味算法:兔子产子问题

趣味算法:兔子产子问题

2010-05-27

假定你有一雄一雌一对刚出生的兔子,它们在长到一个月大小时开始交配,在第二月结束时,雌兔子产下另一对兔子,过了一个月后它们也开始繁殖,如此这般持续下去。每只雌兔在开始繁殖时每月都产下一对兔子,假定没有兔子死亡,在一年后总共会有多少对兔子?

在一月底,最初的一对兔子交配,但是还只有1对兔子;在二月底,雌兔产下一对兔子,共有2对兔子;在三月底,最老的雌兔产下第二对兔子,共有3对兔子;在四月底,最老的雌兔产下第三对兔子,两个月前生的雌兔产下一对兔子,共有5对兔子;……如此这般计算下去,兔子对数分别是:1, 1, 2, 3, 5, 8, 13, 21, 34, 55,89, 144, ...看出规律了吗?从第3个数目开始,每个数目都是前面两个数目之和。这就是著名的斐波那契(Fibonacci)数列。

C 编程解决如下:

#include <stdio.h>
 
int Fib(int n)
{
	if (n == 1 || n == 2)
		return 1;
	else
		return Fib(n-1) + Fib(n-2);
}

int main()
{
	int wait;
	printf("一年可以有 %d 对兔子\n", Fib(12));

	scanf("%d",&wait);
	return 0;
}

有许多相关问题:

有一段楼梯有10级台阶,规定每一步只能跨一级或两级,要登上第10级台阶有几种不同的走法?

答:这就是一个斐波那契数列:登上第一级台阶有一种登法;登上两级台阶,有两种登法;登上三级台阶,有三种登法;登上四级台阶,有五种方法……所以,1,2,3,5,8,13……登上十级,有89种。

数列中相邻两项的前项比后项的极限是多少,就是问,当n趋于无穷大时,F(n)/F(n+1)的极限是多少?

答:这个可由它的通项公式直接得到,极限是(-1+√5)/2,这个就是所谓的黄金分割点,也是代表大自然的和谐的一个数字。

Fibonacci数列的数学表达式就是:F(n) = F(n-1) + F(n-2),F(1) = 1,F(2) = 1。

Fibonacci数列可以用很直观的二叉递归程序来写,用C++语言的描述如下:

long fib1(int n)
{
	if (n <= 2)
	{
   		return 1;
	}
	else
	{
   		return fib1(n-1) + fib1(n-2);
	}
}

看上去程序的递归使用很恰当,可是在用VC2005的环境下测试n=37的时候用了大约3s,而n=45的时候基本下楼打完饭也看不到结果……显然这种递归的效率太低了。

例如,用下面一个测试函数:

long fib1(int n, int* arr)
{
	arr[n]++;
	if (n <= 2)
	{
 		return 1;
	}
	else
 	{
  		return fib1(n-1, arr) + fib1(n-2, arr);
	}
}

这时,可以得到每个fib(i)被计算的次数:

fib(10) = 1     fib(9) = 1      fib(8) = 2      fib(7) = 3
fib(6) = 5      fib(5) = 8      fib(4) = 13    	fib(3) = 21
fib(2) = 34   	fib(1) = 55    	fib(0) = 34

可见,计算次数呈反向的Fibonacci数列,这显然造成了大量重复计算。

我们令T(N)为函数fib(n)的运行时间,当N>=2的时候我们分析可知:T(N) = T(N-1) + T(N-2) + 2

其实,这违反了递归的一个规则:合成效益法则。

合成效益法则(Compound interest rule):在求解一个问题的同一实例的时候,切勿在不同的递归调用中做重复性的工作。 所以在上面的代码中调用fib(N-1)的时候实际上同时计算了fib(N-2)。这种小的重复计算在递归过程中就会产生巨大的运行时间。

用一叉递归程序就可以得到近似线性的效率,用C++语言的描述如下:

long fib(int n, long a, long b, int count)
{
     if (count == n)
         return b;
     return fib(n, b, a+b, ++count);
}
 
long fib2(int n)
{
     return fib(n, 0, 1, 1);
}

这种方法虽然是递归了,但是并不直观,而且效率上相比下面的迭代循环并没有优势。

Fibonacci数列用迭代程序来写也很容易,用C++语言的描述如下:

//也可以用数组将每次计算的f(n)存储下来,用来下次计算用(空间换时间)
long fib3 (int n)
{
     long x = 0, y = 1;
     for (int j = 1; j < n; j++)
     {
         y = x + y;
         x = y - x;
     }
     return y;
}

这时程序的效率显然为O(N),N = 45的时候 <1s就能得到结果。

随机文章推荐
网站分类


注:如需转载本文,请注明出处(原文链接),谢谢。更多精彩内容,请进入简明现代魔法首页。

进入新博客
喜欢本文,就分享它吧
给我留言
您的名字:
您的邮件:
您的网站:


 

copyright © 2009 简明现代魔法    学习、分享、进步

power by Gonn 感谢所有关心和支持本站的朋友们