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

        今天在 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 
#include 

void matrix_multiply(int *a, int m, int n, int q, int *b, int *r)
{
    int row;
    for(row=0; row0)//此循环的执行次数为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 
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)