zoukankan      html  css  js  c++  java
  • 【算法题】06-用栈来解决汉诺塔问题

    用栈来解决汉诺塔问题

    题目

    修改汉诺塔问题的游戏规则:限制不能从最左侧的塔直接移动到最右侧,也不能从最右侧直接移动到最左侧,而是必须经过中间。求当塔有N层的时候,打印最优移动和最优移动总步数。

    要求:

    • 方法一:递归的方法
    • 方法二:非递归的方法,用栈来模拟汉诺塔的三个塔

    思路

    方法一:递归的方法

    首先,如果只剩最上层的塔需要移动,则有如下处理:

    1. 如果希望从左移动到右,打印Move 1 from left to right
    2. 如果希望从中移动到左,打印Move 1 from mid to left
    3. 如果希望从中移动到右,打印Move 1 from mid to right
    4. 如果希望从右移动到中,打印Move 1 from right to mid
    5. 如果希望从左移动到右,打印Move 1 from left to mid Move 1 from mid to right
    6. 如果希望从右移动到左,打印Move 1 from right to mid Move 1 from mid to left

    以上就是递归的终止条件,也就是只剩上层塔时的打印过程。

    多层塔的时候。

    如果剩下N层塔,从最上到最小依次为1~N-1,则有如下判断

    1. 如果剩下的N层塔都在左,希望全部移到中,则有三个步骤

      1)将1~N-1层塔先全部从左移动到右,交给递归过程

      2)将第N层塔从左移到中

      3)将1~N-1层塔全部从右移到中,明显交给递归过程

    2. 如果把剩下的N层塔从中移到左,从中移到右,从右移到中过程与上述相同

    3. 如果剩下的N层都在左,希望全部移动到右。

      1)将1~N-1层塔先全部从左移动到右,交给递归

      2)将第N层塔从左移动到中

      3)将1~N-1层塔从右移到左

      4)N层从中移到右

      5)最后将1~N-1层塔全部从左移到右,交给递归过程

    4. 如果剩下全在右,希望移到左,同上

    public static int hanoiProblem1(int num, String left, String mid,
    			String right) {
    		if (num < 1) {
    			return 0;
    		}
    		return process(num, left, mid, right, left, right);
    	}
    
    	public static int process(int num, String left, String mid, String right,
    			String from, String to) {
    		//只有一层需要移动的时候
    		if (num == 1) {
    			//如果from或to有一个是mid,说明是从右往中,或从左往中,直接移动即可。
    			if (from.equals(mid) || to.equals(mid)) {
    				System.out.println("Move 1 from " + from + " to " + to);
    				return 1;
    			} else {
    				//否则说明是从左到右,或从右到左,都需要两步,先到中间,再到目的地
    				System.out.println("Move 1 from " + from + " to " + mid);
    				System.out.println("Move 1 from " + mid + " to " + to);
    				return 2;
    			}
    		}
    		//当层数大于1的时候,且有一个是到中间
    		if (from.equals(mid) || to.equals(mid)) {
    			//如果是从左到中,或者中到左 another就为右,否则就为左,就是说现在参与的是左中,那么剩下的一个是右,同理右中
    			String another = (from.equals(left) || to.equals(left)) ? right : left;
    			//递归处理n-1层,从当前的from到另一端,即从做到右,或从右到左
    			int part1 = process(num - 1, left, mid, right, from, another);
    			//将第N层塔从左移到中或从右移到中
    			int part2 = 1;
    			System.out.println("Move " + num + " from " + from + " to " + to);
    			//将1~N-1层塔全部从右移到中,或者从左移动中
    			int part3 = process(num - 1, left, mid, right, another, to);
    			return part1 + part2 + part3;
    		} else {
    			//从左到右或者从右到左  递归处理n-1层
    			//将1~N-1层塔先全部从左移动到右或从右移到左
    			int part1 = process(num - 1, left, mid, right, from, to);
    			//将第N层塔从左移动到中 或从右移到中
    			int part2 = 1;
    			System.out.println("Move " + num + " from " + from + " to " + mid);
    			//将1~N-1层塔从右移到左 或相反
    			int part3 = process(num - 1, left, mid, right, to, from);
    			//N层从中移到右
    			int part4 = 1;
    			System.out.println("Move " + num + " from " + mid + " to " + to);
    			//最后将1~N-1层塔全部从左移到右,交给递归过程
    			int part5 = process(num - 1, left, mid, right, from, to);
    			return part1 + part2 + part3 + part4 + part5;
    		}
    	}
    
    

    方法二:非递归,用栈来模拟

    将左、中、右三个地点抽象成栈,LS、MS、RS,那么栈的操作就可以看成是:某一个栈(from)把栈顶元素弹出,然后压入到另一个栈里(to),作为另一个栈(to)的栈顶。

    1. 游戏的第一个动作一定是L->M,这是显而易见的。

    2. 在走出最小部署过程中的任何时刻,四个动作只有一个动作不违反小压大和相邻不可逆原则,另外三个一定会违反

      上述第二点证明:

      假设前一步的动作是L->M:

      1. 根据小压大的原则,L->M的动作不会重复发生
      2. 根据相邻不可逆原则,M->L的动作也不该发生
      3. 根据小压大的原则,M->R和R->M只会有一个达标

      假设前一步的动作是M->L:

      1. 根据小压大原则,M->L的动作不会重复发生
      2. 根据相邻不可逆原则L->M也不会发生
      3. 根据小压大原则,M->R和R->M只会有一个达标

      假设前一步的动作是M->R:

      1. 根据小压大的原则,M->R不会重复发生
      2. 根据相邻不可逆原则,R->M也不会发生
      3. 根据小压大的原则,L->M和M->L只会有一个达标

      假设前一步的动作是R->M:

      1. 根据小压大的原则,R->M的动作不会重复发生
      2. 根据相邻不可你原则,M->R的动作也不该发生
      3. 根据小压大原则,L->M和M->L只会 有一个达标

      如上,每一步只会有一个动作达标,那么只要每走一步都根据这两个原则考察所有的动作就可以,哪个达标走哪个。

    public static enum Action {
    		No, LToM, MToL, MToR, RToM
    	}
    
    	public static int hanoiProblem2(int num, String left, String mid, String right) {
    		//左栈
    		Stack<Integer> lS = new Stack<Integer>();
    		//中栈
    		Stack<Integer> mS = new Stack<Integer>();
    		//右栈
    		Stack<Integer> rS = new Stack<Integer>();
    		lS.push(Integer.MAX_VALUE);
    		mS.push(Integer.MAX_VALUE);
    		rS.push(Integer.MAX_VALUE);
    		//从num开始由大到小依次入左栈
    		for (int i = num; i > 0; i--) {
    			lS.push(i);
    		}
    		Action[] record = { Action.No };
    		int step = 0;
    		//如果右栈的个数不等于num+1说明还没有移动完
    		while (rS.size() != num + 1) {
    			step += fStackTotStack(record, Action.MToL, Action.LToM, lS, mS, left, mid);
    			step += fStackTotStack(record, Action.LToM, Action.MToL, mS, lS, mid, left);
    			step += fStackTotStack(record, Action.RToM, Action.MToR, mS, rS, mid, right);
    			step += fStackTotStack(record, Action.MToR, Action.RToM, rS, mS, right, mid);
    		}
    		return step;
    	}
    
    
  • 相关阅读:
    WERKZEUG之WSGI阅读笔记
    Express4+Mongodb超简单入门实例
    git 命令小结
    GreenSock Animation Platform
    Nodejs开发框架Express3.0开发手记–从零开始
    交互设计实用指南系列(1) – 操作入口明确
    交互设计实用指南系列(4)—简洁清晰,自然易懂
    交互设计实用指南系列(5) – 突出重点,一目了然
    交互设计实用指南系列(6) –标签明晰、有效
    交互设计实用指南系列(8)—深广度平衡
  • 原文地址:https://www.cnblogs.com/dream-to-pku/p/12427509.html
Copyright © 2011-2022 走看看