有种说法我觉得很好,所谓递归,就是利用大道至简的思想,把一个大的复杂的问题层层转换为一个小的和原问题相似的问题来求解的这样一种策略。
优点 | 缺点 |
---|---|
递归给人的感觉是惊艳,它往往能给我们带来非常简洁非常直观的代码形势,从而使我们的编码大大简化。 | 效率往往很低,费时和费内存空间。 |
在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储。递归次数过多容易造成栈溢出。 | |
递归的思维确实跟我们的常规思维相逆的,我们通常都是从上而下的思维问题, 而递归趋势从下往上的进行思维。 |
汉诺塔问题是我见过的最优美的递归算法
汉诺塔问题的解决步骤分三步:
反复循环直到结束。
void hanno(int n, char A, char B, char C) {
if (n == 1) {
printf("move disk %d from %c to %c\n", n, A, C);
return;
}
hanno(n - 1, A, C, B); //盘子A上面的 n-1 个盘子从A移动到B
printf("move disk %d from %c to %c\n", n, A, C);//把最下面的盘子移动到C
hanno(n - 1, B, A, C); //再把B上的盘子移动回C
}
public class HannoTower {
void start(int n, String from, String cache, String to) {
if (n == 1) {
System.out.println("move " + n + " from " + from + " to " + to);
return;
}
start(n - 1, from, to, cache);
System.out.println("move " + n + " from " + from + " to " + to);
start(n - 1, cache, from, to);
}
}
咋一看是不是很扯??
虽然说原则上步骤只有三步,但是那只是原则上啊。。怎么程序也只有三步??第一步和第三步仅仅只是调用本身就解决问题了是什么鬼??
那些中间过程哪里去了??
别急,我们慢慢分析,上面我们说了,递归的思想是从下往上,从最简单的问题开始:
if (n == 1) {
printf("move disk %d from %c to %c\n", n, A, C);
return;
}
如果有 2 个盘子,可以先将盘子1上的盘子2移动到 B;将盘子1移动到 C;将盘子2移动到 C。这说明了:可以借助空塔将2个盘子从 A 移动到 C,当然,也可以借助 C 将2个盘子从 A 移动到 B。
如果有3个盘子,那么根据2个盘子的结论,可以借助 C 将盘子1上的两个盘子从 A 移动到 B;将盘子1从 A 移动到 C,A 变成空塔;借助 A 塔,将 B 上的两个盘子移动到 C。
以此类推,上述的思路可以一直扩展到 n 个盘子的情况,将将较小的 n-1个盘子看做一个整体,也就是我们要求的子问题,以借助 C 塔为例,可以借助塔 C 将盘子 A 上面的 n-1 个盘子从 A 移动到 B;将 A 最大的盘子移动到 C,这时 A 变成空塔;再借助塔 A,将 B 上面的 n-1 个盘子移动到 C。
hanno(n - 1, A, C, B); //塔A上面的 n-1 个盘子从A移动到B
printf("move disk %d from %c to %c\n", n, A, C);//把最下面的盘子移动到塔C
hanno(n - 1, B, A, C); //再把塔B上的盘子移动到塔C
至此,程序结束,实际当中那么多的步骤,程序只用3行。。。
在我看来,这个问题的精髓在于ABC三塔的相对变化,所谓的ABC塔只是一个相对称呼,在不同层级的递归函数中,他们是不同的,也可以简单理解为,A塔是原点,B塔是缓存,C塔是目的。
move 1 from A to C
move 2 from A to B
move 1 from C to B
move 3 from A to C
move 1 from B to A
move 2 from B to C
move 1 from A to C