今天在 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楼说的,这个是有误差的。
————————————————————————————————————————————————————————————————————————————
(未完)
唉,开学了,要找工作喽~~~