算法学习笔记:递归

递归

  • 为了描述问题的某一状态,必须用到该状态的上一状态,而描述上一状态,又必须用到上一状态的上一状态……这种用自已来定义自己的方法,称为递归。
  • 递归往往能快速简洁的描述出一个问题的数学模型。比如阶乘公式: f ( n ) = n f ( n 1 ) f(n) = n*f(n-1) ,学过高中数学的都清楚,如果用代数方法求阶乘,我们必须还有由递归公式推导出代数公式,当时在编程当中,借助于计算机强大的计算能力,我们只需利用递归公式,就可以完成变成。

有种说法我觉得很好,所谓递归,就是利用大道至简的思想,把一个大的复杂的问题层层转换为一个小的和原问题相似的问题来求解的这样一种策略。

优点 缺点
递归给人的感觉是惊艳,它往往能给我们带来非常简洁非常直观的代码形势,从而使我们的编码大大简化。 效率往往很低,费时和费内存空间。
在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储。递归次数过多容易造成栈溢出。
递归的思维确实跟我们的常规思维相逆的,我们通常都是从上而下的思维问题, 而递归趋势从下往上的进行思维。

结构

  1. 递归的边界条件,也叫递归出口,他决定了递归什么时候结束
  2. 递归的规则,他必须让问题逐渐向边界条件靠拢,否则就会陷入死循环。

汉诺塔问题

  • 古代有一个梵塔,塔内有三个座A、B、C,A座上有64个盘子,盘子大小不等,大的在下,小的在上。
  • 有一个和尚想把这64个盘子从A座移到B座,但每次只能允许移动一个盘子,并且在移动过程中,3个座上的盘子始终保持大盘在下,小盘在上。
  • 当所有的金片都从梵天穿好的那根针上移到另外一根针上时,世界就将在一声霹雳中消灭,而梵塔、庙宇和众生也都将同归于尽。

算法

汉诺塔问题是我见过的最优美的递归算法

汉诺塔问题的解决步骤分三步:

  1. 把A除了最下面的盘子以外的所有盘子都移动到B
  2. 把最下面的盘子移动到C
  3. 再把B上的盘子移动回A

反复循环直到结束。

C++代码:

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
}

java代码:

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);
	}
}

咋一看是不是很扯??
虽然说原则上步骤只有三步,但是那只是原则上啊。。怎么程序也只有三步??第一步和第三步仅仅只是调用本身就解决问题了是什么鬼??
那些中间过程哪里去了??

别急,我们慢慢分析,上面我们说了,递归的思想是从下往上,从最简单的问题开始:

  • 如果只有 1 个盘子,则不需要利用 B 塔,直接将盘子从 A 移动到 C 。这是最小的子问题,也是递归出口。
	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
发布了45 篇原创文章 · 获赞 46 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/founderznd/article/details/51615493