数据结构学习-递归初接触

来源:互联网 发布:淘宝hot什么意思 编辑:程序博客网 时间:2024/04/29 05:57


                  数据结构学习-递归初接触
学习资料-<<数据结构与算法分析-C++版>>,<<算法I-IV-基础,数据结构,排序与搜索>>
一:递归概述
如果一种算法调用自己来完成它的部分工作,就称这种算法是递归的(recursive)。这种方法要想取得成功,必须在比原始问题小的问题上调用自己。总而言之,一个递归算法必须有两个部分:初始情况(base)和递归部分。初始情况只处理可以直接解决而不要再次递归调用的简单输入。递归部分则包含对算法的一次或者多次递归调用,每一次的调用参数都在某种程度上更接近初始情况。
二:递归用法举例:
(1):计算n!
这个例子十分简单:它基于n!=n*(n-1)!
long fact(int n)
{
 if(n<=1) return 1;
 return  n*fact(n-1);
}
它的非递归版本为:
long fact(int n)
{
 int result=1;
 for(int i=1;i<=n;i++)
 {
  result*=i;
 }
 return result;
}
(2):Fibonacci数列的求解
比如要列举出第N个Fibonacci数:
int Fib(int N)
{
 if(N==1) return 1;
 if(N==2) return 1;
 return Fib(N-1)+Fib(N-2);
}

它的非递归版本为:
int Fib(int N)
{
 if(N==1 || N==2) return 1;
 int a=1;
 int b=1;
 for(int i=3;i<=N;i++)
 {
  int temp=a;
  a=b;
  b=temp+b;
 }
 return b;
}

  
(3):欧几里德算法求两数的最大公约数
作为有2000年历史的最老的著名算法之一,这是一个找出两个整数的最大公约数的递归方法,它基于这样一种观察:两个整数x和y(x>y)的最大公约数等同于y与x mod y的最大公约数。t除以x和y等价于t除以y和x mod y,因为x等同于x mod y加上一个y的倍数。对于该算法而言,递归的深度由参数的属性算术(即参数的对数)决定。
int gcd(int m,int n)        //要求m>n
{
 if(n==0) return m;
 return gcd(n,m%n);
}
这是对碾转相除法的一种运用,我能想到的非递归实现求两数的最大公约数的只有这个笨办法了:
int gcd(int m,int n)    //同样要求m>n
{
 for(int i=n;i>0;i--)
 {
  if(m%i==0 &&n%i==0)
  {
   return i;
  }
 }

(4):Hanoi塔的递归解法
前面两个递归实现看上去并不那么复杂,因为用一个while循环就可以达到同样的效果。下面给出一个计算Hanoi塔的解法问题。它的实现有多个递归调用,而且不是那么容易就能用while循环改写的。
如果不费劲去考虑细节,这个问题是非常容易的。只要考虑所有的圆盘必须从start柱移到end柱上,因此必须首先把最下面(最大)的盘移到end柱上。要达到这个目的,end柱必须是空的,而且start柱上只能有最下面一个圆盘,因此其余的n-1的圆盘只能在temp柱上。假设X是一个函数,可以把start柱上面的n-1个圆盘移动到tmp柱上,然后把start柱最下面一个圆盘移动到end柱上,最后,再用函数X把其余的n-1个圆盘移动到end柱上即可。在这两种情况中,“函数X”只不过是调用更小问题的hanoi塔函数而已。成功的秘密在于汉诺塔算法为我们做了这些工作。我们不必关心汉诺塔的子问题如何解决这些细节,只要做好两件事,问题就迎刃而解了。第一,必须有一个初始情况(如果只有一个圆盘怎么做),以便使递归过程不会永远进行下去。第二,对汉诺塔问题的递归调用只能用来解决更小的问题,而且只有一种正确形式(一种满足汉诺塔问题初始定义的形式,假定对柱子适当地重命名)。

void TOH(int n,Pole start,Pole goal,Pole temp)
{
 if(n==0) return;
 TOH(n-1,start,temp,goal);
 move(start,goal);
 TOH(n-1,temp,goal,start);
}
(5):递归与链表操作
先看下面的4个函数

int count(LINK x)
{
 if(x==0) return 0;
 return   1+count(x->next);
}

void traverse(LINK h)
{
 if(h==0) return;
 visit(h);
 traverse(h->next);
}

void traverseR(LINK h)
{
 if(h==0) return;
 visit(h);
 traverse(h->next);
}

void remove(LINK & x,Item v)
{
 while(x!=0 && x->Item==v)
 {
  LINK t=x;
  x=x->next;
  delete t;
 }
 if(x!=0) remove(x->next,v);
}

第一个函数计算链表中的节点数。每二个函数从头到尾为每一个链表上的节点调用函数visit()。这两个函数都很容易用while或for循环来实现。第三个函数并无一个简单的循环可以与之对应。它为链表中每一个节点调用函数visit。但用相反的顺序进行。第四个函数从链表中消除带有给定值的节点。该实现的关键在每一个要被删除的节点的前驱中能改变x=x->next的这样一种链接,由于使用引用参数使实现成为可能。


(6):一个有问题的递归程序
如果参数N是奇数,这个函数用3N-1作为参数调用本身,如果N是偶数,函数用N/2作为参数调用本身。我们不能使用归纳法证明该程序终止,,因为并非每一个递归调用都使用一个比上面所给的更小的参数。
如果没有N的范围,我们就无从知道这种计算对于每个N是否会终止。对于能表示为int的小整数,我们已经检验过了它会终止,但是对于大整数,(比如64位的数),我们无从得知这个程序是否会进入无限循环当中去。
int puzzle(int N)
{
 if(N==1) return 1;
 if(N%2==0)
 {
  return puzzle(N/2);
 }
 else
 {
  return puzzle(3*N+1);
 }
}
我做得实验每次都以16,8,4,2,1,结束递归,我也记得有一个数学趣味题就是用得这个原理,可一时记不起来了。Please tell me if you know!

三:递归算法的运行时间问题
递归算法的运行时间问题跟普通的循环时间计算稍有一点不同,总之最后归结为级数求和形式,现在我也不会写什么要求高效率的程序,所以以后碰到了再来看书,反正我已经知道在哪能得到这方面的知识了,呵呵。