递归算法的理解
To Iterate is Human, to Recurse, Divine.
人理解迭代,神理解递归。
当第一次看到汉诺塔(Hanoi)如此简洁的算法时,你不得不被递归的魅力所吸引,是那样的简洁。我想它的迷人之处正是在于用有限的语句来定义对象的无限集合。
递归:程序调用自身的编程技巧称为递归( recursion)。一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法。 ----百度百科
如我们所熟知的斐波那契数列的定义就是具有递归特性:
- 0, n=0
- 1, n=1
- Fib(n-1)+Fib(n-2), n>1
那么现在我们要如何计算后面的函数值呢,比如:
Fib(100)=?
首先,我们知道
Fib(0)=0
Fib(1)=1
Fib(100)=Fib(99)+Fib(98);
Fib(99)=Fib(98)+Fib(97);
…
如此往复,当最终化为Fib(0)和Fib(1)时,然后将值带进去。
最终得出我们想要的答案
用c语言描述:
int Fib(int n)//斐波那契函数
{
if(n==0)
return 0;
else if (n==1)
return 1;
else
return Fib(n-1)+Fib(n-2);
}
我们现在能够发现这个函数实质上包含两个过程,递去(不断深入递归的层数),再归来。
递归实质上分为两个过程:
递去:将递归问题分解为若干个规模较小,与原问题形式相同的子问题,并且这些子问题可以用相同的解题思路来解决。
归来:当你将问题不断递去的时候,必须有一个明确的结束递去的临界点(递归出口),一旦达到这个临界点即从该点返回,最终解决。
递 和 归,这正是递归的精华所在!
知道了递归的含义,我们来比较一下循环和递归的区别:
先对递归和循环形象化的描述:
递归:你打开面前这扇门,看到屋里面还有一扇门。你走过去,发现手中的钥匙还可以打开它,你推开门,发现里面还有一扇门,你继续打开它。若干次之后,你打开面前的门后,发现只有一间屋子,没有门了。然后,你开始原路返回,每走回一间屋子,你数一次,走到入口的时候,你可以回答出你到底用这你把钥匙打开了几扇门。
循环:你打开面前这扇门,看到屋里面还有一扇门。你走过去,发现手中的钥匙还可以打开它,你推开门,发现里面还有一扇门(若前面两扇门都一样,那么这扇门和前两扇门也一样;如果第二扇门比第一扇门小,那么这扇门也比第二扇门小,你继续打开这扇门,一直这样继续下去直到打开所有的门。但是,入口处的人始终等不到你回去告诉他答案。
我们再通过对n的阶乘的循环表示和递归表示:
循环:
int factor=1;
while(n){
factor*=n;
n--;
}
递归:
int Factor(n)
{
if(n==1)
return 1;
else
return n*Factor(n-1);
明显区别:递归是需要不断地调用自身,调用自身函数所花费的开销相较于循环是巨大的(循环不需要函数调用),所以在选择时,需要考虑花销的情况下,优先考虑循环。
用递归的方式来实现汉诺塔
代码:
#include<iostream>
using namespace std;
void harro(int X,char a,char b,char c);
void move_one(int n,char a,char b);
extern int num=0;
int main (){
int n;
cout<<"请输入汉诺塔层数:"<<endl;
cin>>n;
harro(n,'A','B','C');
cout<<"一共为:"<<num<<"步"<<endl;
return 0;
}
void harro(int n,char a,char b,char c){
if(n==1) move_one(n,a,c);
else {
harro(n-1,a,c,b);
move_one(n,a,c);
harro(n-1,b,a,c);
}
}
void move_one(int n,char a, char b){
cout<<n<<"个将"<<a<<"移到"<<b<<endl;
num++;
}