下面是一个算法题目:
有100个台阶,每次可以走1个台阶,也可以走2个台阶,问走完这100个台阶共有多少种走法?
分析:
假设n代表台阶的数目,sum(n)表示走法的数目,则有如下关系:
1。n=1,则表示只有1个台阶,则只有一种走法,则sum(n)=1;
2。n=2,则表示有2个台阶,这个时候,可以选择一次走两个台阶,或者连续走两次,每次走1个台阶,则sum(n)走法为2。
3。n不为1,也不为2,这个时候有两种情况,一种情况是,上次是走的1个台阶。另外一种情况是,上次是一次走的2个台阶。
于是,sum(n) = sum(n-1) + sum(n-2)。
于是,归纳如下:
sum(n) = 1, n = 1;
= 2, n = 2;
= sum(n-1) + sum(n-2), n != 1 且 n != 2
于是,算法可以这样写:
int sum(int n)
{
if (n == 1) {
return 1;
}
else if (n == 2) {
return 2;
}
else if (n > 0) {
return (sum(n-1) + sum(n-2));
}
else {
return 0;
}
}
注意:这里只考虑了算法的实现,没有考虑实际的值可能超过int可以表示的范围的问题。
可以看出,这是一个菲波拉齐数列,可以以非递归的方法来给出一个新的算法。
新的算法如下:
int sum(int n)
{
int store[2];
int index;
int ptr;
index = 0;
if (n <=0 )
return 0;
if (n == 1) {
return 1;
}
else if (n == 2) {
return 2;
}
else {
store[0] = 1;
store[1] = 2;
for (ptr = 3; ptr <= n; ptr++) {
if (index == 0) {
store[0] += store[1];
index = 1;
}
else {
store[1] += store[0];
index = 0;
}
}
if (index == 0)
return store[1];
else
return store[0];
}
}
这里看起来显得有些冗长,但实际上思路是简单而清晰的,也就是利用了两个临时存储单元来轮转存储,从而降低对额外存储空间的需求。
对于这种比较容易得到非递归算法的程序而言,可以以比较简单的方式得到其非递归的算法。
但对于很多的递归算法而言,得到其非递算法就没有那么容易了。下面介绍一般意义上可以利用的办法。
下面我们仔细分析递归算法的一些执行过程,并从中获得对于我们有益的一些东西:
int sum(int n)
{
if (n == 1) {
return 1;
}
else if (n == 2) {
return 2;
}
else if (n > 0) {
return (sum(n-1) + sum(n-2));
}
else {
return 0;
}
}
通过分析,我们发现递归法的一般思路就是将一个规模比较大的问题转换成比较小规模,容易解决的问题,然后将小规模问题的解逐渐回放,最后得到比较大规模问题的解。
在这里算法里,计算sum(n),必然要用到对sum(n-1)和sum(n-2)的求解,计算sum(n-1),必然要用到对sum(n-2)和sum(n-3)的求解,但实际上,这里面有些问题的求解是反复进行的,造成了大量的浪费。
从本算法来看,计算sum(n),必然要用到对sum(n-1)和sum(n-2)的求解。但sum(n-1)和sum(n-2)的求解的规模与sum(n)类似。于是这几乎是指数的增长规模。
于是,一个新的思路诞生了,为减少运算的规模,可以考虑将计算出的结果保存在数组里面。当下一次需要用到某个计算结果时,如果该结果已经计算出来了,则可以直接使用该计算结果。
下面是新的算法:
long sum(int n)
{
long total;
if (cache[n] != -1) {
return cache[n];
}
if (n == 1) {
cache[n] = 1;
return 1;
}
else if (n == 2) {
cache[n] = 2;
return 2;
}
else if (n > 0) {
total = (sum(n-1) + sum(n-2));
cache[n] = total;
return total;
}
else {
cache[n] = 0;
return 0;
}
}
这个算法需要事先定义一个结果数组,用于存放已经计算出来的sum的结果,可以以static的方式存放。
例如:
static int cache[MAX_SIZE];
定义为static的好处是可以使其他模块中的文件无法看到这个数组。
然后,在调用sum函数之前,必须先初始化这个cache数组,将各个项初始化为-1的值。
有100个台阶,每次可以走1个台阶,也可以走2个台阶,问走完这100个台阶共有多少种走法?
分析:
假设n代表台阶的数目,sum(n)表示走法的数目,则有如下关系:
1。n=1,则表示只有1个台阶,则只有一种走法,则sum(n)=1;
2。n=2,则表示有2个台阶,这个时候,可以选择一次走两个台阶,或者连续走两次,每次走1个台阶,则sum(n)走法为2。
3。n不为1,也不为2,这个时候有两种情况,一种情况是,上次是走的1个台阶。另外一种情况是,上次是一次走的2个台阶。
于是,sum(n) = sum(n-1) + sum(n-2)。
于是,归纳如下:
sum(n) = 1, n = 1;
= 2, n = 2;
= sum(n-1) + sum(n-2), n != 1 且 n != 2
于是,算法可以这样写:
int sum(int n)
{
if (n == 1) {
return 1;
}
else if (n == 2) {
return 2;
}
else if (n > 0) {
return (sum(n-1) + sum(n-2));
}
else {
return 0;
}
}
注意:这里只考虑了算法的实现,没有考虑实际的值可能超过int可以表示的范围的问题。
可以看出,这是一个菲波拉齐数列,可以以非递归的方法来给出一个新的算法。
新的算法如下:
int sum(int n)
{
int store[2];
int index;
int ptr;
index = 0;
if (n <=0 )
return 0;
if (n == 1) {
return 1;
}
else if (n == 2) {
return 2;
}
else {
store[0] = 1;
store[1] = 2;
for (ptr = 3; ptr <= n; ptr++) {
if (index == 0) {
store[0] += store[1];
index = 1;
}
else {
store[1] += store[0];
index = 0;
}
}
if (index == 0)
return store[1];
else
return store[0];
}
}
这里看起来显得有些冗长,但实际上思路是简单而清晰的,也就是利用了两个临时存储单元来轮转存储,从而降低对额外存储空间的需求。
对于这种比较容易得到非递归算法的程序而言,可以以比较简单的方式得到其非递归的算法。
但对于很多的递归算法而言,得到其非递算法就没有那么容易了。下面介绍一般意义上可以利用的办法。
下面我们仔细分析递归算法的一些执行过程,并从中获得对于我们有益的一些东西:
int sum(int n)
{
if (n == 1) {
return 1;
}
else if (n == 2) {
return 2;
}
else if (n > 0) {
return (sum(n-1) + sum(n-2));
}
else {
return 0;
}
}
通过分析,我们发现递归法的一般思路就是将一个规模比较大的问题转换成比较小规模,容易解决的问题,然后将小规模问题的解逐渐回放,最后得到比较大规模问题的解。
在这里算法里,计算sum(n),必然要用到对sum(n-1)和sum(n-2)的求解,计算sum(n-1),必然要用到对sum(n-2)和sum(n-3)的求解,但实际上,这里面有些问题的求解是反复进行的,造成了大量的浪费。
从本算法来看,计算sum(n),必然要用到对sum(n-1)和sum(n-2)的求解。但sum(n-1)和sum(n-2)的求解的规模与sum(n)类似。于是这几乎是指数的增长规模。
于是,一个新的思路诞生了,为减少运算的规模,可以考虑将计算出的结果保存在数组里面。当下一次需要用到某个计算结果时,如果该结果已经计算出来了,则可以直接使用该计算结果。
下面是新的算法:
long sum(int n)
{
long total;
if (cache[n] != -1) {
return cache[n];
}
if (n == 1) {
cache[n] = 1;
return 1;
}
else if (n == 2) {
cache[n] = 2;
return 2;
}
else if (n > 0) {
total = (sum(n-1) + sum(n-2));
cache[n] = total;
return total;
}
else {
cache[n] = 0;
return 0;
}
}
这个算法需要事先定义一个结果数组,用于存放已经计算出来的sum的结果,可以以static的方式存放。
例如:
static int cache[MAX_SIZE];
定义为static的好处是可以使其他模块中的文件无法看到这个数组。
然后,在调用sum函数之前,必须先初始化这个cache数组,将各个项初始化为-1的值。