【算法笔记】使用栈实现汉诺塔(Hanoi)经典算法
2017-09-28 23:44
585 查看
汉诺塔(Hanoi)算法,应该是每一个程序员都会学习到的递推算法之一,汉诺塔是一个很著名的智力题,但是这里就不科普它的由来了,我们直接进入正题。
如上图,假设A棒有五个原盘,依次移动,每次移动一块,小的永远只能在上面,最终移动到C棒上,如何用算法实现呢?
从这里移动的逻辑我们很容易发现,A帮不就像一个栈吗,栈顶必须先出,网上看过很多汉诺塔算法,很少涉及到用栈实现,的确,算法拿出来了,用什么都一样,在我学习的时候,教材上是用的char,直接模拟推算,没用真正移动数据实现真正的Hanoi思想,所以,琢磨了一会,写了一个用栈实现的算法。
首先,既然是栈,为了方便跟踪,写了一个自己的MyStack包装了一下Java的Stack,贴上代码:
然后就是Hanoi递推的实现,还是贴上图片
我们要按照Hanoi的逻辑将原盘从A棒移动到C棒,那么,就必须以B棒作为媒介,
最大的必须在最下面,所以,我们必须把上面4个圆盘先移动到B棒上。
但是又看,上面4个,要想把第四个移动到B棒,就得用C棒暂时当媒介,先把上面3个移动到C棒,以此类推,知道只有最后一个的时候,就可以直接移动了,所以,我们要做的就是想出一个算法,递推到只剩一个圆盘,然后慢慢回栈,到第二,第三,第四,最后第五。
先贴上代码:
首先解释参数中的size,因为栈无法在不取出元素的情况下递减长度,所以增加了参数size作为栈的圆盘指针,限定只能移动size个圆盘。
所以,当size==1的时候,就是只剩下一个圆盘,那么就顺理成章的直接移动到C棒了,不必在意此时的C棒回栈后是B棒还是A棒,那不是此时递归该担心的事情。
在算法中,如果size!=1,那么说明我们需要一个作为媒介,让size-1个圆盘先暂时放到媒介上,然后将第size个圆盘放过去,所以,我们需要进行一次递归,将第size-1个上面的圆盘重新进行计算该存放的位置,计算完成后,然后放入第size个圆盘到C帮,然后再将媒介B棒中的圆盘又以A棒为媒介,以此方式放入C盘。
至于为什么要在递归前缓存一次B棒的size,因为进入递归前不知道B棒是否有数据,说不定此次计算正是上一次的递归呢,不知道后面的方法B棒会是怎样的存在,不知道会进入多少次递归,假设B棒在进入第一次递归前长度为2,递归完后,长度为5,第二次递归时,如果不限制size长度,直接使用B棒的size,那么,除了第一次递归时增加的3个数据,还会把原本的2个数据一起计算进去,博主就在这个坑绕了一些时间,最后跟踪了一下才明白这个。
文字有点多,最后贴上完整代码:
核心算法只有那一段,其他是我为了方便跟踪增加的,各位在测试的时候可以选择性删除。
如上图,假设A棒有五个原盘,依次移动,每次移动一块,小的永远只能在上面,最终移动到C棒上,如何用算法实现呢?
从这里移动的逻辑我们很容易发现,A帮不就像一个栈吗,栈顶必须先出,网上看过很多汉诺塔算法,很少涉及到用栈实现,的确,算法拿出来了,用什么都一样,在我学习的时候,教材上是用的char,直接模拟推算,没用真正移动数据实现真正的Hanoi思想,所以,琢磨了一会,写了一个用栈实现的算法。
首先,既然是栈,为了方便跟踪,写了一个自己的MyStack包装了一下Java的Stack,贴上代码:
class MyStack{ private String name; private Stack<Integer> data; public MyStack(String name){ this.name=name; data=new Stack<>(); } public String getName(){ return this.name; } public void push(int data){ if(!this.data.isEmpty()&&this.data.peek()<data){ System.out.println("出错"); } this.data.push(data); } public int peek(){ return data.peek(); } public int pop(){ return data.pop(); } public int size(){ return data.size(); } public boolean isEmpty(){ return data.isEmpty(); } }
然后就是Hanoi递推的实现,还是贴上图片
我们要按照Hanoi的逻辑将原盘从A棒移动到C棒,那么,就必须以B棒作为媒介,
最大的必须在最下面,所以,我们必须把上面4个圆盘先移动到B棒上。
但是又看,上面4个,要想把第四个移动到B棒,就得用C棒暂时当媒介,先把上面3个移动到C棒,以此类推,知道只有最后一个的时候,就可以直接移动了,所以,我们要做的就是想出一个算法,递推到只剩一个圆盘,然后慢慢回栈,到第二,第三,第四,最后第五。
先贴上代码:
public static void hanoi(int size,MyStack a,MyStack b,MyStack c){ if(size==1){ c.push(a.pop()); }else{ int n=b.size(); hanoi(size-1,a,c,b); c.push(a.pop()); hanoi(b.size()-n,b,a,c); } }
首先解释参数中的size,因为栈无法在不取出元素的情况下递减长度,所以增加了参数size作为栈的圆盘指针,限定只能移动size个圆盘。
所以,当size==1的时候,就是只剩下一个圆盘,那么就顺理成章的直接移动到C棒了,不必在意此时的C棒回栈后是B棒还是A棒,那不是此时递归该担心的事情。
在算法中,如果size!=1,那么说明我们需要一个作为媒介,让size-1个圆盘先暂时放到媒介上,然后将第size个圆盘放过去,所以,我们需要进行一次递归,将第size-1个上面的圆盘重新进行计算该存放的位置,计算完成后,然后放入第size个圆盘到C帮,然后再将媒介B棒中的圆盘又以A棒为媒介,以此方式放入C盘。
至于为什么要在递归前缓存一次B棒的size,因为进入递归前不知道B棒是否有数据,说不定此次计算正是上一次的递归呢,不知道后面的方法B棒会是怎样的存在,不知道会进入多少次递归,假设B棒在进入第一次递归前长度为2,递归完后,长度为5,第二次递归时,如果不限制size长度,直接使用B棒的size,那么,除了第一次递归时增加的3个数据,还会把原本的2个数据一起计算进去,博主就在这个坑绕了一些时间,最后跟踪了一下才明白这个。
文字有点多,最后贴上完整代码:
public class Hanoi {
private static int m=1;
private static MyStack a=new MyStack("A");
private static MyStack b=new MyStack("B");
private static MyStack c=new MyStack("C");
public static void main(String[] args) {
for(int i=5;i>0;i--){
a.push(i);
}
hanoi(a.size(),a,b,c);
print(c);
}
public static void hanoi(int size,MyStack a,MyStack b,MyStack c){
if(size==1){
System.out.println("第"+m+++"步,从"+a.getName()+"移动了 "+a.peek()+"到了"+c.getName());
c.push(a.pop());
}else{
int n=b.size();
hanoi(size-1,a,c,b);
System.out.println("第"+m+++"步,从"+a.getName()+"移动了 "+a.peek()+"到了"+c.getName());
c.push(a.pop());
hanoi(b.size()-n,b,a,c);
}
}
public static void print(MyStack temp){
System.out.println("size:"+temp.size());
for(int i=0,n=temp.size();i<n;i++){
System.out.print(temp.pop()+" ");
}
System.out.println();
}
}
class MyStack{ private String name; private Stack<Integer> data; public MyStack(String name){ this.name=name; data=new Stack<>(); } public String getName(){ return this.name; } public void push(int data){ if(!this.data.isEmpty()&&this.data.peek()<data){ System.out.println("出错"); } this.data.push(data); } public int peek(){ return data.peek(); } public int pop(){ return data.pop(); } public int size(){ return data.size(); } public boolean isEmpty(){ return data.isEmpty(); } }
核心算法只有那一段,其他是我为了方便跟踪增加的,各位在测试的时候可以选择性删除。
相关文章推荐
- Scala深入浅出实战经典《第89讲:Scala中使用For表达式实现内幕思考》笔记
- 基于GPU实现的经典光照模型算法:漫反射模型(使用cg语言实现)
- C++实现汉诺塔算法经典实例
- 【面试经典题之字符串】实现一个算法,确定一个字符串的所有字符是否全都不同。假设不允许使用额外的数据结构
- 汉诺塔(hanoi)算法实现
- 经典算法问题:“this is a test” 逆序输出为“test a is this”.使用Python实现
- C语言经典算法(十)——递归实现汉诺塔
- 重温经典的迷宫算法实现---栈的使用
- 韩顺平_PHP程序员玩转算法公开课(第一季)07_使用数组实现堆栈_学习笔记_源代码图解_PPT文档整理
- python实现经典算法(1):汉诺塔
- 用递归法:设计算法求解汉诺塔问题,并编程实现。 (1) Hanoi(汉诺)塔问题分析 这是一个古典的数学问题,是一个用递归方法解题的典型例子。问题是这样的:古代有一个梵塔,塔内有3个座 A,B,C
- Scala深入浅出实战经典《第88讲:Scala中使用For表达式实现map、flatMap、filter》笔记
- Castle学习笔记----使用HQL语句实现复杂查询
- 近期准备写一系列关于使用python实现常用算法的文章
- 一个无聊男人的疯狂《数据结构与算法分析-C++描述》学习笔记 习题2.8 随机数组的三种生成算法(补) 将bash的实现翻译成比较纯正的bash风格
- 使用正向最大匹配算法实现中文分词简单模型-用trie树实现
- 一个无聊男人的疯狂《数据结构与算法分析-C++描述》学习笔记 用C++/lua/python/bash的四重实现(7)习题2.8 随机数组的三种生成算法
- 学习笔记:使用Web Service Software Factory开发简易留言本服务以及Mobile调用实现-1.创建Service
- FreeBSD学习笔记12-pureftpd使用详解(1)-安装、配置、实现匿名登录