概念:
函数可以直接或间接地调用自身,称为递归调用。
所谓直接调用自身,就是指在一个函数的函数体中,出现了对自身的调用表达式。
直接调用自身:
void fun1()
{
fun1(); //调用fun1自身
}
间接调用自身:
void fun1()
{
fun2();
}
void fun2()
{
fun1();
}
递归的过程有如下两个阶段:
第一阶段:递推。将原问题不断分解为新的子问题,逐渐从未知向已知推进表,最终达到已知的条件,即递归结束的条件,这时递推阶段结束。
第二阶段:回归。从已知条件出发,按照地推的逆过程,逐一求值回归,最后达到递推的开始处,结束回归阶段,完成递归调用。
汉诺塔问题:
有三根针A,B,C,A针上有n个盘子,盘子大小不等,大的在下,小的在上,如图所示。要求把这n个盘子从A针移到C针,在移动过程中可以借助B针,每次只允许移动一个盘,且在移动过程中在三根针上都保持大盘在下,小盘在上。
一、分析
当n=3时,需要把A上的盘子借助B移到C上。
如果最大的盘子需要移到C,则意味着其他所有盘子按顺序从小到大依次叠放B上,因此,可以得到最后一层示意图如下:
此时,我们需要把A上剩下的一个盘子移到C上,再将B上的两个盘子移到C上。A—>C显而易见,因此现在把问题聚焦到B上的两个盘子。现在的问题相当于把B上的两个盘子借助A移到C上。这是一个和原问题类似的子问题,它的解决方法和上面的思路类似,即进入递归的过程。因此,汉诺塔问题可以分为以下3个步骤:
- 将A上n-1个盘子移到B针上(借助C针)。
- 把A针上剩下的一个盘子移到C针上。
- 将n-1个盘子从B针移到C针上(借助A针)。
事实上,上面三个步骤包含下面两种操作:
- 将多个盘子从一个针移到另一个针上,这是一个递归的过程。
- 将1个盘子从一个针上移到另一个针上。
结合上述分析,“把A上的3个盘子借助B移到C上”"把B上的2个盘子借助A移到C上"属于(1);"A—>C"属于(2)。
二、代码
2.1 构造函数
用hanoi函数实现第一种操作(递归),用move函数实现第二种操作。
//n层汉诺塔
//if n==0 时,递归过程结束
//一个起始柱,一个中间柱,一个目标柱
void move(char start, char destination)
{
cout << start << "--->" << destination<<endl;
}
//将n个盘子从A通过B移到C
void hanoi(int n, char start, char medium, char destination)
{
if (n == 0)
return;
else
{
//将n-1个盘子从A通过C移到B
hanoi(n - 1, start, destination, medium);
//将剩下一个盘子从A移到C
//输出操作
move(start, destination);
//将n-1个盘子从B通过A移到C
hanoi(n - 1, medium, start, destination);
}
}
主函数:
int main()
{
int n;
cout << "Enter the number of diskes:";
cin >>n;
hanoi(n, 'A', 'B', 'C');
return 0;
}
运行结果:
三、递归过程
总结与反思:
递归问题解决的关键在于分解找出子问题,而不在于在脑子里模拟出每一步是如何运行的,尤其面对更复杂的问题时,要靠人脑解决全过程是十分困难的。递归的过程其实就是不断调用函数的过程,在这方面,程序的运行思路是严格依照代码运行顺序执行的,和人脑的思路不太一样,所以递归问题千万不能复杂化,不需要执意弄清楚每一步是如何产生的,而是要找准递归的子问题,分析递归过程。