【算法】【栈和队列模块】使用栈结构和递归两种方式求解汉诺塔问题
前言
当前所有算法都使用测试用例运行过,但是不保证100%的测试用例,如果存在问题务必联系批评指正~
在此感谢左大神的书让我对算法有了新的感悟认识!
问题介绍
原问题
给定整数n作为圆盘个数,现在需要将n个圆盘从左边移动到右边,注意,不能从左边直接到右边,一次只能走一步,打印最优轨迹
解决方案
方案一:递归方式
1、将倒数第二个盘及以上盘作为一个整体,问题就简化成了2个盘的问题了,整体移动使用递归即可,非常简单
方案二:栈方式
首先每一轮都只有四种选择走法:
* 1、左-中、中-左、中-右、右到中
* 2、每一个状态都面临上面四种抉择
* 3、四种抉择中通过以下2条定律就能够筛选出能够使用的唯一那条路
* a)移动不违反大压小原则,需要过去的圆盘不能比目标栈的栈顶大
* b)相邻不可逆:如果前一个动作是左-中,那么下一个动作一定不是中-左,并且不可能重复上一个步骤(违反大压小)
* 总结:任何时刻,四个动作都只有一个满足ab原则,剩下三个一定违反原则
方案三:DP版本请看另一篇文章~
代码编写
java语言版本
方案一
public class HannioTower {
/**
* 入口函数
* @param num 汉诺塔数量
* @param left 左边:可以是字符串
* @param mid 中间
* @param right 右边
* @param from 起点
* @param to 终点
* @return
*/
public int hanioProblem(int num, String left, String mid, String right, String from, String to){
if(num < Constants.NUM_1){
return Constants.NUM_0;
}
return this.process(num, left, mid, right, from, to);
}
/**
* 递归函数
* @param num
* @param left
* @param mid
* @param right
* @param from
* @param to
* @return
*/
private int process(int num, String left, String mid, String right, String from, String to){
// 终止条件,就一个的时候为终止
if(num == Constants.NUM_1){
if(from.equals(mid) || to.equals(mid)){
// 其中一个点是中间,则只需要一步即可
System.out.println("Move 1 from :" + from + " to: " + to);
// 返回步数
return Constants.NUM_1;
}else{
// 都不是中点的情况移动两步,一定会经过中间
System.out.println("Move 1 from :" + from + " to: " + mid);
System.out.println("Move 1 from :" + mid + " to: " + to);
return Constants.NUM_2;
}
}
// 如果数量超过一个,则不是终止条件
if(from.equals(mid) || to.equals(mid)){
/**
* 有一个是中间,分三步走:
* 1、先移动num-1层到另外一边(交给递归)
* 2、底层移动到目标
* 3、num-1移动到目标(交给递归)
*/
// 另一边一定是终点或者起点的对立面(其中有一个是mid)
// 这个other作为对立面,总会存在from-other,from-mid,other-from,mid-to,from-to
String other = (from.equals(left) || to.equals(left)) ? right : left;
// num-1起点到对立面
int part1StepNum = process(num-1, left, mid, right, from, other);
// 最后一层到达目标
System.out.println("Move " + num + " from :" + from + " to: " + to);
int part2StepNum = Constants.NUM_1;
// num-1起点到终点
int part3StepNum = process(num-1, left, mid, right, other, to);
return part1StepNum + part2StepNum + part3StepNum;
}else{
/**
* 从一边到另外一边
* 1、先移动num-1 到另外一边(递归)
* 2、移动num到中间
* 3、再移动num-1 到起点
* 4、移动num到终点
* 5、移动num-1到终点
*/
// num-1层移动到终点
int part1StepNum = process(num - 1, left, mid, right, from, to);
// num移动到中间
System.out.println("Move " + num + " from :" + from + " to: " + mid);
int part2StepNum = Constants.NUM_1;
// num-1移动到起点
int part3StepNum = process(num -1, left, mid, right, to, from);
// 移动num到终点
System.out.println("Move " + num + " from :" + mid + " to: " + to);
int part4StepNum = Constants.NUM_1;
// 移动num-1到终点
int part5StepNum = process(num -1, left, mid, right, from, to);
return part1StepNum + part2StepNum + part3StepNum + part4StepNum + part5StepNum;
}
}
/**
* 测试用例
* @param args
*/
public static void main(String[] args) {
HannioTower hannioTower = new HannioTower();
hannioTower.hanioProblem(2, "left", "mid", "right", "left", "right");
}
}
方案二:
public class HanoiTower {
/**
* 方法二:栈方式实现
* 首先每一个盘都有四种选择走法
* 1、左-中、中-左、中-右、右到中
* 2、每一个状态都面临上面四种抉择
* 3、四种抉择中通过以下2条定律就能够筛选出能够使用的唯一那条路
* a)移动不违反大压小原则,需要过去的圆盘不能比目标栈的栈顶大
* b)相邻不可逆:如果前一个动作是左-中,那么下一个动作一定不是中-左,并且不可能重复上一个步骤(违反大压小)
* 总结:任何时刻,四个动作都只有一个满足ab原则,剩下三个一定违反原则
* @param num
* @param left
* @param mid
* @param right
*/
public static int hanoiTower2(int num, String left, String mid, String right){
Stack<Integer> lS = new Stack<>();
Stack<Integer> mS = new Stack<>();
Stack<Integer> rS = new Stack<>();
// 盘底需要最大值
lS.push(Integer.MAX_VALUE);
mS.push(Integer.MAX_VALUE);
rS.push(Integer.MAX_VALUE);
// 先将圆盘放入到最左边的柱子上
for (int i = num; i > 0; i--){
lS.push(i);
}
// 上一个动作
ActionEnum[] record = new ActionEnum[]{ActionEnum.NO};
// 总步数
int step = 0;
// 每一次都是四种尝试
while (rS.size() < num + 1){
step += fStack2tStack(record, ActionEnum.M2L, ActionEnum.L2M, lS, mS, left, mid);
step += fStack2tStack(record, ActionEnum.L2M, ActionEnum.M2L, mS, lS, mid, left);
step += fStack2tStack(record, ActionEnum.R2M, ActionEnum.M2R, mS, rS, mid, right);
step += fStack2tStack(record, ActionEnum.M2R, ActionEnum.R2M, rS, mS, right, mid);
}
return step;
}
/**
* 判断走法
* @param record
* @param pre
* @param now
* @param fStack
* @param tStack
* @param from
* @param to
* @return
*/
private static int fStack2tStack(ActionEnum[] record, ActionEnum pre, ActionEnum now, Stack<Integer> fStack, Stack<Integer> tStack, String from, String to) {
if (record[0] != pre && fStack.peek() < tStack.peek()){
System.out.printf("Move %d from %s to %s\n", fStack.peek(), from, to);
tStack.push(fStack.pop());
record[0] = now;
return 1;
}
return 0;
}
/**
* 测试用例
* @param args
*/
public static void main(String[] args) {
// HanoiTower hanoiTower = new HanoiTower();
// 两个盘从左边到右边
HanoiTower.hanoiTower2(2, "left", "mid", "right");
}
}
c语言版本
正在学习中
c++语言版本
正在学习中
思考感悟
方案一:中的思路就是递归的另一种看法,一种的栈的方式看递归,每一层都是一个状态,这种方式就是将问题变化为一个基本的步骤和一个整体的步骤,最基本的需要实现(最底部的盘的运动),剩下的看做一个整体(n-1个盘的运动),问题就简化成了两个盘的运动了。
方案二:
说实话,方案二是我根本想不到的方法,感谢左大神开拓思路,那么想一下为什么能想到这种办法呢?这种办法有什么原理呢?以后能解决什么类似的问题呢?悟一下是学习中最重要的环节!
回答1:首先方案一的递归会让人联想到栈,那么栈的思路就来了,这个我想过,但是不知道怎么实现。
回答2:重要的原理就是栈结构的进出规则类似于汉诺塔的进出规则!那么肯定要三个栈代表汉诺塔的三个柱子;这里感慨一下汉诺塔的空柱子和栈的最大的底Integer.MAX_VALUE的设计达到同样的效果,记住这个方法!;之后就是最难想的步骤,该如何制定逻辑能够走出正确的步伐,其实汉诺塔的最优解一定就只有一条路,并且这条路的每一步都有一个范围,那就是四种走法,然后往下尝试发现每一次四种走法都只有一种可以走!问题得到解答!
回答3:我觉得能够知道四种走法的时候问题就解决了一半,所以以后遇到最优解路径的问题时,可以尝试自己先走一下最优解,然后想一下为什么最优解在这一步的时候要这么走?不这么走会怎么样,从而产生出一个”原则“!也就是后面要形成的判断逻辑!
写在最后
方案和代码仅提供学习和思考使用,切勿随意滥用!如有错误和不合理的地方,务必批评指正~
如果需要git源码可咨询2260755767@qq.com或在评论区回复即可.
再次感谢左大神的书对我算法的指点迷津!
相关文章
- 光谱的处理算法
- 【算法】【字符串模块】求长度为num,0左边一定是1的str种数
- 【算法】【字符串模块】字符串中的调整和替换
- 【算法】【字符串模块】字符串中的数字子串求和
- 【算法】【递归与动态规划模块】数组字符串转字母的所有种数
- 【算法】【递归与动态规划模块】字符串之间转换的最小代价
- 【算法】【递归与动态规划模块】两个字符串的最长公共子数组
- 【算法】【递归与动态规划模块】理解汉诺塔问题以及中间态判断
- 【算法】【递归与动态规划模块】斐波那契数列的系列问题解法及递推类型问题的最优解
- 【算法】【二叉树模块】在二叉树中找到累加和为指定值的最长路径长度
- 【算法】【链表模块】二叉树空间复杂度为O(1)的遍历方法(Morris算法)
- 【算法】【链表模块】一种奇怪的方式删除单链表中的值
- 【算法】【链表模块】使用单链表进行选择排序
- 【算法】【链表模块】复制含有其他引用的单链表节点
- 【DA算法】基于DA算法的FIR滤波器的FPGA实现
- 88 C++ - 常用查找算法
- LeetCode左程云算法课笔记(整理ing)
- 常见的垃圾回收算法
- [算法]LeetCode 120:三角形最小路径和
- KM算法小结
- Python数学运算的一个小算法(求一元二次方程的实根)
- baselines算法库baselines/bench/monitor.py模块分析
- Matlab PCA 算法
- 21天经典算法之直接插入排序
- 括号匹配算法求解(用栈实现)
- 八大排序算法总结
- 算法2.1 假设利用两个线性表LA和LB分别表示两个集合A和B(即线性表中的数据元素即为集合中的成员),现要求一个新的集合A=AUB。这就要求对线性表作如下操作:扩大线性表LA。。。