园子里面试题目盛行,今天看到一个关于“兔子”数量的问题,回复很多,但感觉都不得要领,答案也是千奇百怪、五花八门。这里给出我的一个写法。
题目:
一对小兔子一年后长成大兔子;一对大兔子每半年生一对小兔子。大兔子的繁殖期为4年,兔子的寿命是6年。假定第一年年初投放了一对小兔子,试编程计算,第n年末总共会有多少对兔子。n由键盘输入。(用递归哦,谢谢!)(http://www.cnblogs.com/zuozuo/archive/2011/09/18/2180426.html)
1.理解问题
每次看到这种题目,我总是胆战心惊,担心自己把题目理解错。自然语言中的“一年后”、“每半年”、“繁殖期为4年”、“寿命是6年”以及“第n年末”其实一不小心就会弄错,而一旦弄错,结果必然谬之千里。
编程的首要原则是什么?我认为是理解问题(Understand the problem first!参见:http://programmers.stackexchange.com/questions/91527/what-do-you-consider-the-1st-principles-of-programming)。在考虑算法之前,必须理解要解决的问题是什么。如果连问题都不清楚,怎么可能谈得上算法呢?
很多人一旦拿到题目,就立刻认为自己已经理解了问题。然而对于我来说,我宁愿花几倍的时间来理解问题。下面就是我对题目的理解过程:
首先我假设一个具体的起始时间(人笨,只好用笨办法):2000年1月1日。从这天起有了最初的那1对小兔子(并且假设它们为0岁。实际上还可以是半岁,这一点题目并未明确,应该算是题目本身的一个漏洞)。由于“大兔子每半年生一对小兔子”且“兔子的寿命是6年”,6年是半年的整数倍数,所以兔子的数量每半年变化一次。因此每半年考察一次兔子的数目。
0岁 半岁(+) 1岁 1.5岁 2岁 2.5岁 3岁 3.5岁 4岁 4.5岁 5岁 5.5岁 (总)
00年01月01日 1 1
00年07月01日 1 1 第1年末
01年01月01日 1 1 2
01年07月01日 1 1 1 3 第2年末
02年01月01日 2 1 1 1 5
02年07月01日 3 2 1 1 1 8 第3年末
03年01月01日 5 3 2 1 1 1 13
03年07月01日 8 5 3 2 1 1 1 21 第4年末
04年01月01日 13 8 5 3 2 1 1 1 34
04年07月01日 21 13 8 5 3 2 1 1 1 55 第5年末
05年01月01日 33 21 13 8 5 3 2 1 1 1 88
05年07月01日 54 33 21 13 8 5 3 2 1 1 1 142 第6年末
06年01月01日 86 54 33 21 13 8 5 3 2 1 1 227
06年07月01日 139 86 54 33 21 13 8 5 3 2 1 1 366 第7年末
(注:用EXCELL做这个表极其方便)
由于“兔子的寿命是6年”,所以不可能有6周岁的兔子
由于“繁殖期为4年”,所以只有1~4.5岁的兔子有生育能力
特别要注意的是,“第n年末”的含义则意味着经历了2*n-1次繁殖。所谓第7年年末就是06年7月1日到06年12月31日之间的数量。这个期间兔子数量没有变化。
2.代码
根据前面的分析,这个题目的算法很简单,几乎没有研讨的必要。重要的是建立适当的数据结构。
用一个数组来表示各年龄兔子的数目即可:
unsigned rabbits [12];
其中,rabbits[0]表示0岁兔子数目,rabbits[1]表示半岁兔子数目……,由于不可能有6周岁的兔子,所以数组只需要12个数据。
每过半年,兔子长半岁。意味着数组各个元素依次向后移动一个位置。最后一个数据,由于不存在超过6周岁的兔子,所以舍弃。每次移动后根据rabbits[2]~rabbits[9](繁殖期为4年)的总数计算新出生的兔子数目(rabbits[0])。
计算第n年末兔子的数目,用一个简单的2*n-1次循环就可以完成,这实在谈不上什么算法。不过既然题目要求递归,那么好吧
/*
题目:
一对小兔子一年后长成大兔子;一对大兔子每半年生一对小兔子。
大兔子的繁殖期为4年,兔子的寿命是6年。
假定第一年年初投放了一对小兔子,试编程计算,第n年末总共会有多少对兔子。
n由键盘输入。(用递归哦,谢谢!)
*/
#include <stdio.h>
#include <stdlib.h>
#define COUNT_IN_HALF(years) ( 2 * ( years ) )
#define GROW_UP ( COUNT_IN_HALF(1) )//一对小兔子一年后长成大兔子
#define REPRODUCTION_BEGIN ( GROW_UP )
#define REPRODUCTION_END ( GROW_UP + COUNT_IN_HALF(4) )//大兔子的繁殖期为4年
#define LIFE_END ( COUNT_IN_HALF(6) ) //兔子的寿命是6年
#define SIZE(a) ( sizeof(a)/sizeof(*a) )
unsigned breed ( unsigned [], size_t , unsigned );
unsigned sum ( unsigned [], size_t );
void move ( unsigned [], size_t );
int main( void )
{
unsigned rabbits [LIFE_END] //不存在满6周岁的兔子
= { 1 } ;
unsigned n ;
printf("即算第几年末的兔子数?\n");
scanf("%u",&n);
printf( "%u\n" ,
breed ( rabbits , SIZE( rabbits ) , COUNT_IN_HALF( n ) )
);
system("PAUSE");
return 0;
}
unsigned breed ( unsigned rabbits[] , size_t size , unsigned half_years )
{
if( half_years == 1 ) // half_years - 1 次
return sum( rabbits , size );
move( rabbits , size - 1 ); //兔子长半岁
rabbits[0] = sum ( rabbits + GROW_UP , REPRODUCTION_END - GROW_UP ); //繁殖
return breed( rabbits , size , half_years - 1 );
}
//索性也递归
unsigned sum ( unsigned rabbits[] , size_t size )
{
if( size == 0)
return 0 ;
return sum( rabbits , size - 1 ) + rabbits[size - 1];
}
//将递归进行到底 ,呵呵
void move ( unsigned rabbits[] , size_t i )
{
if( i == 0 )
return ;
rabbits[ i ]=rabbits[ i-1 ];
move ( rabbits , i - 1 );
}
感想:1.先看懂题。2.良好的数据结构往往不需要什么复杂的算法。
顺便说一下,这个面试题目基本和下面那道题目基本完全一样
http://www.cnblogs.com/KBTiller/archive/2011/07/15/2107161.html#2175878