兔子、母牛繁殖问题(递归、非递归)

原创 2012年09月03日 00:10:22

        今天在 http://topic.csdn.net/u/20120828/12/8336bd43-4a3c-4b77-bf17-2fa854c3702e.html 看到个问题:

     一头母牛从出生后,每两年可以生下一头母牛,即在第二年和第四年分别可产下一头母牛,出生后第五年将会死去。假设农场现有一头母牛,N年后农场的母牛数目是多少,编写程序实现。

        我首先想到的是兔子数列(斐波那契数列),兔子数列的大致意思是:兔子在出生两个月后,就有繁殖能力,一对兔子每个月能生出一对小兔子来。如果所有兔都不死,那么一年以后可以繁殖多少对兔子?兔子和母牛的区别在于:兔子一个月繁殖一次,母牛两年繁殖一次;兔子需要两个月的缓冲期才能繁殖,母牛生下来就会;兔子不会死,母牛只能活5年。

        回想一下,初触兔子数列已经是中学时期了(应该是学习递归数列通项公式的时候),当时是如何推导出兔子数列的通项公式的呢?忘了,我能记起来的方式就是数学归纳法了:找规律,然后证明!(7种方式实现斐波那契数列

先找一找兔子数列的规律:

月份 0   1    2    3    4    5    6    7    8    9   10    11    12    13    14    15
总数 1   1    2    3    5    8    13   21   34   55  89    ...  
把数据列出来之后,就可以抛开兔子的繁殖方式什么的,这只是一堆有规律的数字,要做的就是找出它的规律。

这个规律还是比较好找的(忘记当初学习的时候是不是轻松找到的。。):f(0) = f(1) = 1,  f(n) = f(n - 2) + f(n - 1) when(n >= 2), f(n) = 0 when(n < 0).

把这个规律(公式)写成函数:

int Fib(int n)
{
	if (n == 1 || n == 0)
	{
		return 1;
	}
	else
	{
		if (n > 0)
		{
			return (Fib(n - 1) + Fib(n - 2));
		}
		else
		{
			return 0;
		}
	}
}
接下来看看这个母牛数列的规律:
年份    0     1     2     3     4     5     6     7     8    9    10     11     12   13     14
总数    1     1     2     2     4     3     6     5     10   8    16     13     26   21     42
抛开母牛,从这一堆数字中找规律:对于奇数年份,总数分别是:1、 2、 3、 5、 8、13 ... 就是除去第一项的兔子数列;对于偶数年份,总数都是它前一年的两倍:

f(0) = f(1) = 1,  f(2n + 1) = f(2n - 1) + f(2n - 3) when (n >= 1),  f(2n) = 2 * f(2n - 1) when(n >= 1), f(n) = 1 when(n < 0).

int Cow(int n)
{
	if(n == 1 || n == 0 || n == -1)
	{
		return 1;
	}
	else
	{
		if (n > 0)
		{
			if (n % 2 == 0)
			{
				return (2*Cow(n - 1));
			}
			else
			{
				return (Cow(n - 2) + Cow(n - 4));
			}
		}
		else
		{
			return 0;
		}
	}
}

在斐波那契数列的计算中,递归的效率是很低的:多余的重复计算 和 压栈、出栈的开销。所以试着使用循环来代替递归:

int FibLoop(int n)
{
	int n1 = 1, n2 = 1, n3, i;
	if (n >= 2)
	{
		for (i = 1; i < n; i++)
		{
			n3 = n1 + n2;
			n1 = n2;//将每次求得的结果保存起来,避免重复计算
			n2 = n3;//
		}
		return n3;
	}
	else
	{
		if (n >= 0)
		{
			return 1;
		}
		else
		{
			return 0;
		}
	}
}
母牛的非递归:
int CowLoop(int n)
{
	int nPPPP = 1, nPPP = 1, nPP = 1, nP = 1, key, i;
	if (n >= 2)
	{
		for (i = 2; i <= n; i++)
		{
			if (i%2 == 0)
			{
				key = 2*nP;
			}
			else
			{
				key = nPPPP + nPP;
			}
			nPPPP = nPPP;//保存结果,避免重复计算
			nPPP = nPP;//
			nPP = nP;//
			nP = key;//
		}
		return key;
	}
	else
	{
		if (n >= 0)
		{
			return 1;
		}
		else
		{
			return 0;
		}
	}	
}

    

        以上就是我自己的思路了:看到母牛繁殖问题 --> 联想到自己已经解决的与这个问题类似的兔子数列 --> 使用解决兔子数列的方法(数学归纳法)去解决此问题 --> 优化解决方案(将递归改为循环) --> 思考、交流和总结。


看看别人对这个问题的回复:

—————————————————————————————————————————————————————————————————————————————

#1楼:

N < 5: 1
N >= 5: 0
因为农场只有一头母牛,没公牛它怎么生?

看到这个回复真是恍然大悟,自己怎么没注意呢?这题是不是脑筋急转弯.. 回到题目中仔细看一下,题目中没有提到公牛,只说有一头母牛,公牛的数量并没有给出。这样的话,还得先判断有没有公牛,没有公牛的话这个农场有没有人工受精(#7楼说的)技术,母牛之间搞基会不会大肚子,农场的母牛是不是刚出生(#20楼说的),农场够不够大......问题要全面考虑!

—————————————————————————————————————————————————————————————————————————————

#3楼:

链表吧。。一年遍历一次,该生的生,该死的死。。。

我的理解是:第0年,链表只有一个节点,该节点中的数据部分表示该节点的存活时间,0年;

第1年,链表只有一个节点,该节点的数据+1;

第2年,链表有两个节点,第一个节点的数据+1变为2,第二个节点的数据为0;

。。。

每过一年就遍历一次整个链表,每个节点的数据都+1,统计整个链表中共有多少个节点的数据是2或4,就在链表的末尾添加多少个新节点,新节点的数据部分为0;统计整个链表中节点数据为5的节点,free掉这些节点。最终的总数就是链表中节点的总数。

由于每年都要遍历这个链表,这个算法的时间复杂度应Ω(n^2),因为当n>9时,链表上节点的数量是比n大的。(我没有去验证)

———————————————————————————————————————————————————————————————————————

#41楼:

int CowCntAftNyears(int n)
{
    int i,sum;
    int a[5]={1,0,1,0,2}; /* increment */
    if(n<0)return -1;
    switch(n)
    {
        case 0:
        case 1:
            return 1;
        case 2:
        case 3:
            return 1+1;
        case 4:
            return 1+1+2;
        default:
            for(i=5;i<=n;i++)
            {
                a[0]=a[1];
                a[1]=a[2];
                a[2]=a[3];
                a[3]=a[4];
                a[4]=a[0]+a[2];  /* 改了这一行 */
            }
            return a[0]+a[1]+a[2]+a[3]+a[4];
    }
}


这道题思路是这样的。
先建立一个数组用来存储增量,第奇数年增量是0(先不考虑死亡),偶数年是一个斐波那契数列。
牛的寿命是5岁,可以用一个长度是5的数组作为滑动块在增量数组上滑动(就像过去人们用的计算尺的感觉),我们可以这样理解这个数组,某年0岁的有几头?1岁的有几头?2岁的有几头?3岁的有几头?4岁的有几头?总数就是这一年有几头活牛。
我们可以发现如果不是频繁的滑动,这个滑动操作完全可以用移动数组中的数据来模拟,效率也不会太低,而且省了增量数组的空间。这对于需要算的几百万年后的人很重要。
有人可能会说,这么多年,算出的结果早就越界了。但越界问题我们还用自定义大数类型的方法来解决,而空间不够可就无解了。所以我觉得,如果严谨一点的话,这道题用链表的、递归的都不可取。当然随便玩玩无所谓。

作者的思路说得很清楚了!

————————————————————————————————————————————————————————————————————————————

#32楼:

#include <stdlib.h>
#include <stdio.h>

void matrix_multiply(int *a, int m, int n, int q, int *b, int *r)
{
    int row;
    for(row=0; row<m; ++row)
    {
        int column;
        for(column=0; column<q; ++column)
        {
            int i;
            r[row*m+column] = 0;
            for(i=0; i<n; ++i)
            {
                r[row*m+column] += a[row*m+i]*b[i*n+column];
            }
        }
    }
}

typedef int (*int_star_5)[5];

void main()
{
    unsigned int year = 36;//年数

    int a[5] = {1,0,0,0,0};
    int b1_t[5][5] = {{0,1,0,0,0},{1,0,1,0,0},{0,0,0,1,0},{1,0,0,0,1},{0,0,0,0,0}};
    int b2_t[5][5];
    int r_t[5][5] = {{1,0,0,0,0},{0,1,0,0,0},{0,0,1,0,0},{0,0,0,1,0},{0,0,0,0,1}};

    int_star_5 b1 = b1_t;
    int_star_5 b2 = b2_t;
    int_star_5 r = r_t;

    int count = 0;
    while(year>0)//此循环的执行次数为log(year),所以时间复杂度为O(logn)。
    {
        if((year&0x01)==1)
        {
            int k;
            for(k=count; k>0; --k)
            {
                matrix_multiply(&b1[0][0], 5, 5, 5, &b1[0][0], &b2[0][0]);
                
                int_star_5 b_temp = b1;
                b1 = b2;
                b2 = b_temp;
            }

            matrix_multiply(&r[0][0], 5, 5, 5, &b1[0][0], &b2[0][0]);

            int_star_5 b_temp = b2;
            b2 = r;
            r = b_temp;

            count = 1;
        }
        else
        {
            ++count;
        }        
        year >>= 1;
    }

    int a_temp[5];
    matrix_multiply(&a[0], 1, 5, 5, &r[0][0], &a_temp[0]);
    int sum = 0;
    int i;
    for(i=0; i<5; ++i)
    {
        sum += a_temp[i];
    }
    printf("%d\n", sum);
}

这个我还看不懂,不知道这里的矩阵是什么意思,还要请教一下作者。

————————————————————————————————————————————————————————————————————————————

#33楼

long func(int years)
{
    long tab[5] = {1};
    long i, total = 1;
    for (i = 1; i <= years; ++i)
    {
        total += (tab[1] + tab[3] - tab[4]);
        printf("%d years later total = %d\n", i, total);
        memmove(tab + 1, tab, (sizeof(tab) / sizeof(tab[0]) - 1) * sizeof(tab[0]));
        tab[0] = tab[2] + tab[4];
    }
    return total;
}
作者从年龄的角度出发,tab[0]表示1岁的母牛总数,tab[1]表示2岁的母牛总数...
          tab    [0]   [1]    [2]    [3]     [4]
         年龄     1     2      3      4       5
第1年    数量     1     0      0      0       0
第2年    数量     0     1      0      0       0
第3年    数量     1     0      1      0       0
...
for循环中,total = total +( tab[1] + tab[3]) - tab[4] 表示今年的总数是 去年的总数 加上 今年新生的总数 减去 今年死去的总数。

memmove的作用是将数组右移一位,即每过一年,1岁的总数变为之前0岁的总数,2岁的总数变为之前1岁的总数...

tab[0] = tab[2] + tab[4] 意思是 0岁的总数是今年2岁和4岁的总数。

确实简单明了,程序结构简单,表达的意思明了!

————————————————————————————————————————————————————————————————————————————

#44楼

设斐波那契数列函数Fab(0)=1,Fab(1)=1,Fab(N)=Fab(N-1)+Fab(N-2){N>=2}
N年牛的数量为(2-N%2)*Fab((N+1)/2){N>=1}

这个直接使用斐波那契数列的值了,而不是改造斐波那契数列:  

int Fib(int n)
{
	//使用递归或循环实现
}
int Cow(int n)
{
	if(n == 1 || n == 0 )
	{
		return 1;
	}
	else
	{
		return (2-n%2)*Fib((n+1)/2);
	}
}

————————————————————————————————————————————————

#70楼

#include <math.h>
double GetCowCnt(int n)
{
    if (n%2 == 1)
    {
        return (pow((1+sqrt(5.0)) / 2, (n+1)/2+1) - pow((1-sqrt(5.0)) / 2, (n+1)/2+1)) / sqrt(5.0);
    }
    else
    {
        return 2*((pow((1+sqrt(5.0)) / 2, (n)/2+1) - pow((1-sqrt(5.0)) / 2, (n)/2+1)) / sqrt(5.0));
    }
}
作者根据结果找出规律,跟#44楼的道理一样,只不过这里使用了斐波那契数列的通项公式:

但是像#86楼说的,这个是有误差的。

————————————————————————————————————————————————————————————————————————————

(未完)



唉,开学了,要找工作喽~~~















版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

c++:母牛生小母牛问题(递归)

  • 2009年01月17日 18:02
  • 1KB
  • 下载

兔子繁殖问题 - 两种递归思路

兔子繁殖问题 - 两种递归思路 有一对兔子出生,从第三月起,每个月生一对兔子,出生的兔子也是第三月起每个月生一对兔子,请问2年后,共有多少只兔子? 分析思路—— 斐波那契...

递推算法--兔子的繁殖,逆推、递归--猴子吃桃的问题

解决思路: 通过公式计算到下一个的值,一直到我们要的结果为止1.兔子的繁殖一般而言,兔子在出生两个月之后就有繁殖能力了一对兔子每个月能生一对小兔子 如果所有的兔子都不死,那么一年以后总共有...

数据结构-非递归遍历二叉树

  • 2017年11月16日 10:28
  • 216KB
  • 下载

汉诺塔c#非递归运算

  • 2014年12月01日 22:40
  • 33KB
  • 下载

C算法-兔子问题非递归实现

题目及程序:// // main.c // exercise5 // // Created by GRL on 15/9/15. // Copyright (c) 2015年 mm. All ...

非递归遍历二叉树

  • 2012年08月04日 09:37
  • 2KB
  • 下载

一件开心的事情--用递归解决了兔子问题的升级版!

开通博客有段时间了,忙于学习,也就很少写了,今天有件很开心的事情和大家分享下。 今天和同学讨论了关于springMVC的问题之后,突然对于逻辑编程题感兴趣了,想起以前考试中有一题是使用递归方法解决兔...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:兔子、母牛繁殖问题(递归、非递归)
举报原因:
原因补充:

(最多只允许输入30个字)