汉诺塔问题
描述
相传在古印度圣庙中,有一种被称为汉诺塔(Hanoi)的游戏。该游戏是在一块铜板装置上,有三根杆(编号A、B、C),在A杆自下而上、由大到小按顺序放置64个金盘(如图1)。游戏的目标:把A杆上的金盘全部移到C杆上,并仍保持原有顺序叠好。操作规则:每次只能移动一个盘子,并且在移动过程中三根杆上都始终保持大盘在下,小盘在上,操作过程中盘子可以置于A、B、C任一杆上。(图片来自百度百科)
递归思想
将汉诺塔问题的解决方案可以分为3步:
- 把n-1个盘子从Left借助Mid,搬到Right柱子上
- 把剩下的底层盘子从Left移动到Right柱子上
- 把n-1个盘子从mid借助Left移动到Right柱子上
示意图如下:
至于如何把n-1个盘子搬到另一个柱子上,同样参照上面的3步,只不过它整体的变为从Left借助Right移动到Mid(其实就是要搞清楚是从哪里开始,借助哪个柱子,最后到哪个柱子,整体是这样,每一个小分步也是这样的思想,这也就是递归思想)。
具体代码如下:
import java.util.*;
class Solution {
ArrayList<String> result = new ArrayList<String>();//移动结果存入result列表
public void hanoi(String left,String mid,String right,int n){//参数分别代表(起始位置,借助位置,终点位置,需要移动的个数)
if(n==0) return;//如果n=0 返回
hanoi(left,right,mid,n-1);//1.把n-1个盘子从left(第一个参数)借助right(第三个参数),移动到mid(第二个参数)
String t = "move from "+left+" to "+right;//2.把剩下的一个从left(第一个参数)移动到right(第三个参数)
result.add(t);
hanoi(mid,left,right,n-1);//3.把中间n-1个盘子,从mid(第一个参数)借助right(第三个参数),移动到right(第二个参数)
}
public ArrayList<String> getSolution(int n) {
// write code here
hanoi("left","mid","right",n);//函数调用
return result;
}
}
public class Main{
public static void main(String[] args) {
ArrayList<String> result = new ArrayList<String>();
Scanner scan = new Scanner(System.in);
int n = Integer.parseInt(scan.nextLine());
Solution s = new Solution();
result = s.getSolution(n);
for (String ss:result) {
System.out.println(ss);
}
}
}
时间复杂度:O(2^N)。假设把n个盘子搬动耗时T(n),则有T(n)=2T(n-1)+1,从而[T(n)+1]/[T(n-1)+1]=2,对该等比数列求解即可。
空间复杂度:O(N)。递归栈的深度。
非递归思想
(此方法来自牛客)移动过程分为4步:
- 将当前柱子顶部的盘子,移动到顺时针的下一个柱子
- 将当前柱子和剩余的另一根柱子比较,将其中的一根柱子的盘子移动到另一根柱子上(很显然,这种移动方式是固定的)
- 指针指向顺时针的下一根柱子。
- 遇到某一根柱子的盘子满了时,停止循环;否则回到步骤1。
注意点在于当n为奇数时,顺时针的柱子顺序为left,right,mid;当n为偶数时,顺时针的柱子顺序为left,mid,right。
以n=3为例,示意图如下:
具体代码如下:
import java.util.*;
/***
* 非递归汉诺塔
*/
class Pillar{ //柱子类
private String name;
private LinkedList<Integer> stack;
Pillar(){
this.stack = new LinkedList<>();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public LinkedList<Integer> getStack() {
return stack;
}
public void setStack(LinkedList<Integer> stack) {
this.stack = stack;
}
}
class Solve{
LinkedList<String> result = new LinkedList<String>();
public void initPillar(Pillar a,Pillar b,Pillar c,int n){//初始化顺序
for (int i = n; i > 0; i--) {
a.getStack().push(i);
}
a.setName("left");
if(n % 2 ==1){
b.setName("right");
c.setName("mid");
}else{
b.setName("mid");
c.setName("right");
}
}
public void move(Pillar a,Pillar b){ //1.将a顶层盘子移入b柱子中
result.push("move from "+a.getName()+" to "+b.getName());
b.getStack().push(a.getStack().peek());
a.getStack().pop();
}
public void moveOneToOne(Pillar a,Pillar b){ //2.对于输入的两个柱子,将其中一个柱子的顶层盘子移到另一个柱子中
if(a.getStack().isEmpty()){//如果a是空蛮自然只能把b移动到a
move(b,a);
}else if(b.getStack().isEmpty()){//如果b是空蛮自然只能把a移动到b
move(a,b);
}else if(a.getStack().peek() > b.getStack().peek()){//如果a柱子顶层盘子大于b顶层盘子,只能把b->a
move(b,a);
}else {
move(a, b);
}
}
public boolean isEnd(Pillar b,Pillar c,int n){ //若有一个柱子满了,则循环结束
if(b.getStack().size() == n||c.getStack().size()==n){
return true;
}else{
return false;
}
}
void hanoi(int n,Pillar A,Pillar B,Pillar C){
for (int i = 0; !isEnd(B,C,n); i++) {
if(i % 3 ==0){
move(A,B);
if(isEnd(B,C,n)){
break;
}
moveOneToOne(A,C);
}else if(i % 3 ==1){
move(B,C);
if(isEnd(B,C,n)){
break;
}
moveOneToOne(B,A);
}
else{
move(C,A);
if(isEnd(B,C,n)){
break;
}
moveOneToOne(C,B);
}
}
}
}
public class Main{
LinkedList result = new LinkedList();
public static void main(String[] args) {
Pillar a = new Pillar();//left
Pillar b = new Pillar();//mid
Pillar c = new Pillar();//right
Scanner scan = new Scanner(System.in);
int n = Integer.parseInt(scan.nextLine());
Solve s = new Solve();
s.initPillar(a,b,c,n);
s.hanoi(n,a,b,c);
for (String ss: s.result) {
System.out.println(ss);
}
}
}
时间复杂度:O(2^N),因为总的移动次数没有减少,与方法一相比时间复杂度不会降低。
空间复杂度:O(N),开辟了3个数组用来存放盘子编号。