算法设计与分析之分治法解决汉诺塔问题


前言:该篇博客是我《算法设计与分析》课程的期末笔记,将会出一个系列,后续章节请见主页。
其他:如果可以的话,请给我关注收藏点赞三连,嘻嘻谢谢,如果不可以的话请告诉我“下次一定”。
参考资料:[1]王晓东.算法设计与分析(第三版)[M].北京:清华大学出版社,2014
————————————————————————

2 递归与分治策略

2.1 算法介绍

分治法(Divide and conquer):将一个规模较大问题分解为规模较小的子问题,先求解这些子问题,然后将各子问题的解合并得到原问题的解的算法思路。

递归(Recursion):直接或间接地调用自身的算法。

  • 优点:
    • 是一种自然的思考方式
    • 思路清晰
    • 易于实现
  • 缺点:
    • 具体执行步骤难以理解
    • 坏的递归大幅提高算法复杂度

分治与递归的联系:分治法的子问题通常与原问题结构和求解方法相同,可以通过递归的方法求解。

2.2 汉诺塔(Hanoi tower)

2.2.1 问题描述

有3根柱子及n个不同大小的圆盘,最初,所有盘子由上到下、从小到大地套在第一根柱子上。移动圆盘时受到以下限制:
(1) 每次只能移动一个盘子;
(2) 盘子只能从柱子顶端滑出移到下一根柱子;
(3) 盘子只能叠在比它大的盘子上。
请问:将所有盘子从第一根柱子移到最后一根柱子,如何移动才能移动次数最少?需要移动多少次?

2.2.2 问题解决

令三个柱子分别为A、B、C,最初所有的盘子都在A上,从宏观上看,要想把A的盘子全部移动到C,则可以:

  • ① 将n-1个盘子从A移到B
  • ② 将最下面的1个盘子从A移到C
  • ③ 将n-1个盘子从B移动到C

你可能会问,问题要求(1)中明明限制了一次只能移动一个盘子!
这个时候就要用到分治和递归的思想,上述三个步骤中,仅有步骤②是直接执行,其中步骤①和步骤③都要通过递归实现。
我们先说步骤①,如何把n-1个盘子从A移到B?这可以看成一个新的子问题,而这个子问题跟原问题是一样的,所以我们可以利用递归,再次重复上述的三个步骤就行了,但是,要如何设置参数呢?首先,这个子问题中只有n-1个盘子,所以我们只需要把n改为n-1,其次,要想把n-1个盘子从A移到B,就得先把n-2个盘子从A移到C,依次类推往下…
请你注意:在不断递归的过程中,A、B、C的“移动角色"在不断改变,原问题是把n个盘子从A移到C,子问题为把n-1个盘子从A移到B,子子问题是把n-2个盘子从A移到C…
步骤③的原理同步骤②。

建议这段讲解,配合代码一起阅读,将有助于理解。

2.2.3 时间复杂度

时间复杂度:O(2n)
分析:
在这里插入图片描述
我们首先以5个圆盘为例,来分析时间复杂度。
这张图的含义:灰色部分为直接执行的步骤②,黄色部分为递归执行的步骤①和③。圆圈内的数字代表圆盘的个数,圆圈旁的数字代表所需执行的步骤数,左侧的式子代表对应一层的执行步骤数。
这张图的分析:我们从左侧的式子中可以看出,n=5时,就把每一层的执行步骤数加起来,就是总的执行步骤数,为3+7x20+7x21+7x22+6x22
推广至n时,则为3+(20+21+…+2n-3)+6x22=2.5x2n-4,故O(2n)

2.2.3 代码实现

public class HanoiSimple{
	public static void move(Tower A,Tower B,Tower C){
		move(A.size(),A,B,C);
	}
	public static void move(int n,Tower A,Tower B,Tower C){
		if(n==1){
			C.add(A.remove());
		}
		else{
		    /*关键代码:实现递归与分治*/
			move(n-1,A,C,B);//将n-1个盘子从A移到B(继续递归)
			move(1,A,B,C);//将最下面的那个盘子从A移到C(真正执行)
			move(n-1,B,A,C);//将n-1个盘子从B移动到C(继续递归)
		}
	}
}

Tip:这里的代码单独跑是跑不开的,仅供学习参考,还需要测试代码及Tower类的定义。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章