递归
递归是指,在一个函数或数据结构的定义中直接(或间接)地出现定义本身,这是一个不断深入、细化的过程。
递归的思想比较巧妙,需要自己多去思考,或者多体会别人的代码。
1)函数定义
例如:
阶乘函数,n! = n * (n-1) * ... * 1
int Fact(int n)
{
if(n == 0) return 1; /*若n等于0,阶乘等于1*/
else return n * Fact(n-1); /*否则把n-1传入Fact函数,并与n相乘*/
}
斐波拉契数列,第三个数等于前两个数之和
int Fibonacci(int n)
{
if(n == 1||n ==2) return 1; /*前两个数为1*/
else return Fib(n-1) + Fib(n-2); /*n与n的前两个数由同样的方式计算得到*/
}
2)数据结构定义
例如:
链表结点
typedef struct LNode
{
Elemtype data;
struct LNode *next; /*在结点内部定义指向下一个结点的指针*/
} LNode, *LinkList;
利用链表结点的结构遍历表中的每个结点
/*-------改变指针指向--------*/
void Traverse1(LinkList L)
{
while(L != NULL) /*无递归,要循环*/
{
cout<<L->data;
L = L->next;
}
}
/*-------结合函数的递归定义-------*/
void Traverse2(LinkList L)
{
if(L == NULL) cout<<"Error"; /*无循环,要递归*/
else
{
cout<<L->data;
Traverse2(L->next);
}
}
通过观察上面的代码可以发现,递归可以实现循环的功能。
3)问题的递归解法
当处理一个问题时,需要的条件是解决与问题本质相同的问题,那么就可以用到递归算法。
例如:
Hanoi塔问题
现有三个柱子,分别为A、B、C,A柱上从上往下放有由小到大排列的n个圆盘,问题是如何将A柱上的圆盘全部转移到C柱上,且保持圆盘原本的顺序。每次只能移动一个圆盘,且大圆盘必须在小圆盘下面。
【大思路】
1. 以C为辅助柱,先将A的前n-1个圆盘移动到B,然后将第n个圆盘移动到C;
2. 以A为辅助柱,将B的n-1个圆盘移动到C;
3. 第2步包含与第1步相同的操作,只是A与B的“身份”互换,故递归函数中包含前两步的内容。
#include <iostream>
using namespace std;
void move(char main1, int n, char main2)
{
static int timer = 1;
cout<<"第"<<timer++<<"次移动,"<<main1<<n<<main2<<endl;
}
void Hanoi(int n, char A, char B, char C)
{
if(n == 1)
move(A,n,C);
else
{
Hanoi(n-1,A,C,B);
move(A,n,C);
Hanoi(n-1,B,A,C);
}
}
void main()
{
char t1 = 'A', t2 = 'B', t3 = 'C';
int n;
cout<<"请输入圆盘个数n:";
cin>>n;
Hanoi(n,t1,t2,t3);
}
递归工作栈
在高级语言编制的程序中,调用函数与被调用函数之间的链接及信息交换需要栈的支持。
当有多个函数构成嵌套调用时,按照“先调用后返回”的原则,上述函数之间的信息传递和控制转移必须通过栈来实现,即系统将整个程序运行时所需的数据空间安排在一个栈中。
当前正运行的函数的数据区必须在栈顶。
一个递归函数的运行过程类似于多个函数的嵌套调用,只是调用函数和被调用函数是同一个函数。因此,和每次调用相关的一个重要概念是递归函数运行时的“层次”。
为了保证递归函数的正确执行,系统需要设置“递归工作栈”作为整个递归函数运行期间使用的数据存储区。
每一层递归所需信息构成一个工作记录,其中包括所有的实参、所有的局部变量,以及上一层的返回地址。
每进入一层递归,就产生一个新的工作记录入栈。
每推出一层递归,就从栈顶弹出一个工作记录。
当前执行层的工作记录称为“活动记录”。
对于一般的递归过程,可以仿照递归算法执行过程中递归工作栈的状态变化来直接写出相应的非递归算法,即利用栈消除递归过程。