您的位置:首页 > 职场人生

面试算法:依赖堆栈求解汉诺塔问题

2017-03-11 16:14 387 查看
更详细的讲解和代码调试演示过程,请参看视频

如何进入google,算法面试技能全面提升指南

在算法研究中,汉诺塔是非常经典的一道题。主要是它的求解过程,所展现的思维方式极具代表性,它的解法是,你要解决一个大问题,首先把大问题化解成几个容易解决的小问题,把小问题的解决方案进行简单的操作组合就能得到大问题的解,解决小问题的时候,就是把小问题分解成更小的问题,就这么一直分解,直到问题被分解到轻而易举就能处理的程度。

汉诺塔问题就是这种先分解后综合的典型代表。



题目是这样的,有三根杆,其中一根杆子上有n个铁饼,铁饼由小到大依次从上到下排列,如上图。要求你把杆子1上的铁饼挪到杆子2,杆子3可以作为转移铁饼的中转站,当你转移铁饼时,必须保证小铁饼只能放到大铁饼的上头,请你给出转移铁饼的步骤。

要想把n块铁饼挪到杆子2,我们可以把问题分解成小一点的问题,例如我们可以先把n-1 个铁饼挪到杆子3,然后把最后一个铁饼挪到杆子2,再把n-1个铁饼从杆子3挪到杆子2. 于是原来的问题规模是n,我们把它分解成了问题规模为n-1的子问题。

于是问题就转换为如何把n-1个铁饼转移到另一根杆子, 转移n-1个铁饼又可以分解成转移n-2个铁饼,就这么不断的分解下去,最后问题归结为转移1个铁饼,转移1个铁饼,那肯定就是不费吹灰之力就可以完成的。

按照这个设想,我们实现的代码如下:

import java.util.Stack;

public class HanoiMove {

private int from = 0;
private int to = 0;

private Stack<String> hanoiMove = new Stack<String>();
public  HanoiMove(int n, int from, int to) throws Exception {

if (n <= 0 || from == to || from < 0 || from > n || to < 0 || to > n) {
throw new Exception("Invalid parameters");
}

this.from = from;
this.to = to;

moveHanoiStack(this.from, this.to, 1, n);
}

public void printMoveSteps() {
if (hanoiMove.size() == 1) {
System.out.println(hanoiMove.pop());
return;
}

String s = hanoiMove.pop();
printMoveSteps();
System.out.println(s);
}

private void moveHanoiStack(int from, int to, int top, int end) {
String s = "Move ring  " + end + " from stack " + from + " to " + to;
if (end - top == 0) {
hanoiMove.push(s);
return;
}

int other = from;
for (int i = 1; i <= 3; i++) {
if (i != from && i != to) {
other = i;
break;
}
}

moveHanoiStack(from, other, top, end - 1);
hanoiMove.push(s);
moveHanoiStack(other, to, top, end - 1);
}
}


初始化函数HanoiMove需要输入三个参数,第一个参数是要移动的铁饼数量,第二个参数是表示铁饼原来所在的杆子编号,第三个参数表示要移动的目标杆子编号。例如要把杆子1的3个铁饼转移到杆子2,那么初始化函数可以这么写:

HanoiMove(3, 1, 2);


moveHanoiStack用来实现前面我们所讲的转移步骤,它的输入参数这么解读,from表示铁饼所在的杆子编号,to表示铁饼要转移的杆子编号,top表示最上头的铁饼编号,end 表示最下方的铁饼编号,例如,把杆子1上的三块铁饼搬到杆子2,那么对应的调用就是:

moveHanoiStack(1, 2, 1, 3)


因为杆子1有三块铁饼,最上方也就是最小的铁饼,用编号1表示,最下方,也就是最大的铁饼,用编号3表示。

如果top 和 end 相等,那意味着我们只需要搬运一块铁饼,那么这种简单情况,我们直接把铁饼搬过去就可以了。如果搬运的是n块,那么我们先找到中转杆子,这就是for循环要做的事情,for的目的是遍历所有杆子,找到一根杆子,它的编号与from和to不同即可,这根杆子我们用other表示。

于是我们就先把n-1 个铁饼从杆子from 转移到杆子 other, 然后把最底下的铁饼从杆子from 先放到杆子to, 最后再把挪到杆子other上的n-1个铁饼挪到杆子to上,这样我们就实现了把铁饼从杆子from转移到杆子to的目的。

我们看看主入口函数的实现:

public class StackAndQuque {
public static void main(String[] args) {

try {
HanoiMove hanoi = new HanoiMove(3, 1, 2);
hanoi.printMoveSteps();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}


代码模拟的是把杆子1上的三个铁饼挪到杆子2,初始情况如下:

1

2

3

== == ==

1,2,3代表三个铁饼,下面的横线代表三个杆子。上面代码运行后,每一次铁饼转移的方式都已字符串的形式记录在一个栈中,栈最底部的字符串记录第一步转移,倒数第二个字符串记录第二部铁饼的转移,以此类推。

代码的运行结果如下:

Move ring 1 from stack 1 to 2

Move ring 2 from stack 1 to 3

Move ring 1 from stack 2 to 3

Move ring 3 from stack 1 to 2

Move ring 1 from stack 3 to 1

Move ring 2 from stack 3 to 2

Move ring 1 from stack 1 to 2

我们根据上面的步骤把铁饼挪以此,走第一步后情况如下:

2

3 1

== == ==

走第二步,把2号铁饼从杆子1挪到杆子3:

3 1 2

== == ==

走第三部,把1号铁饼从杆子2 挪到杆子3:

3 1

2

== == ==

走第四步,把3号铁饼从杆子1挪到杆子2:

1

3      2


== == ==

走第五步,把1号铁饼从杆子3移到杆子1:

1 3 2

== == ==

走第六步,把2号铁饼从杆子3移到杆子2:

1 2

3

== == ==

走第七步,把1号铁饼从杆子1挪到杆子2:

1

2

3

== == ==

由此,我们正确的把杆子1上的三个铁饼挪到了杆子2,也就是我们的算法是正确的。

汉诺塔问题是NP完全问题,也就是说,它的算法时间复杂度是指数级的,分析如下,假设我们要挪动n个铁饼,把时间标记为T(n), 我们先挪动n-1个铁饼,所需时间就是T(n-1), 然后再挪动一个铁饼,时间为O(1), 然后再挪动n-1个铁饼,时间为T(n-1), 于是我们有:

T(n) = 2*T(n-1) + O(1).

这个公式把T(n)解出来结果为: T(n) = 2^n;

这就意味着,每增加一个铁饼,所需的挪动步骤几乎是原来的两倍。如果我们把入口函数铁饼数3改成4,所得结果如下:

Move ring 1 from stack 1 to 3

Move ring 2 from stack 1 to 2

Move ring 1 from stack 3 to 2

Move ring 3 from stack 1 to 3

Move ring 1 from stack 2 to 1

Move ring 2 from stack 2 to 3

Move ring 1 from stack 1 to 3

Move ring 4 from stack 1 to 2

Move ring 1 from stack 3 to 2

Move ring 2 from stack 3 to 1

Move ring 1 from stack 2 to 1

Move ring 3 from stack 3 to 2

Move ring 1 from stack 1 to 3

Move ring 2 from stack 1 to 2

Move ring 1 from stack 3 to 2

挪动4个铁饼需要15步,恰巧是挪动3个铁饼所需要步骤数的2倍。

本算法中,使用堆栈在一定程度上是多余的,如果去掉堆栈,您知道怎么把这些转移步骤打印出来吗?

更详细的讲解和代码演示,请参看视频。

更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息