《程序员代码面试指南》用栈来求解汉诺塔问题

题目:

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

例如:当塔为两层的时候,最上层塔记作1,最下层的塔记作2,则打印:

Move 1 from left to mid

Move 1 from mid to right

Move 2 from left to mid

Move 1 from right to mid

Move 1 from mid to left

Move 2 from mid to  right

Move 1 from left to mid

Move 1 from mid to right

It will move 8 steps.

我们来使用栈来解决这个问题:

修改后的汉诺塔问题不能让任何塔从“左”直接移动到“右”,也不能直接从“右”移动到“左”,而是要经过中间,也就是说,实际的动作只有4个:“左”到“中”、“中”到“左”、“中”到“右”、“右”到“中”。

现在我们把左、中、右三个地点抽象成栈,依次记为LS、MS、RS。最初所有的塔都压在LS上。那么如上四个动作可以看作是:某一个栈(from)把栈顶元素弹出,然后压入另一个栈中(to),作为这一个栈(to)的栈顶。

例如,如果是7层塔 ,在最初,所有的塔都在LS上,LS从栈顶到栈底依次是1~7,如果现在发生了“左”到“右”的动作,这个动作对应的操作是LS栈将栈顶元素1弹出,然后压入MS栈中,其他元素同理。

一个动作发生的先决条件就是不违反小大的原则。

from弹出的元素num如果想压进to栈中,那么num指必须小于当前栈的栈顶。

还有一个原则不是很明显,但也是非常的重要,叫做相邻不可逆原则:

1.我们很明显将四个动作依次定义为:L->M、M->L、M->R和R->M。

2.L->M和M->L过程互为逆过程,R->M和M->R互为逆过程。

在修改后的汉诺塔游戏中如果想走出最小的步数,那么任何两个相邻的动作都不是互为逆过程。

核心结论:

1.游戏的第一步动作动作一定是L->M。

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

对于结论2,现在进行简单的证明:

因为游戏的第一个动作是L->M,,则以后每一步都会有前一步。

 

假设前一步是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只有一个动作会达标。

代码:

import java.util.Stack;



public class HanNuoTower {

    

    public enum Action{

        No,LToM,MToL,MToR,RToM

    }



    public int hannuoProblem(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);

        for(int i = num; i > 0; i--){

            lS.push(i);

        }

        Action[] record = {Action.No};

        int step = 0;

        while(rS.size() != num + 1){

            step += fStackToStack(record, Action.MToL, Action.LToM, lS, mS, left, mid);

            step += fStackToStack(record, Action.LToM, Action.MToL, mS, lS, mid, left);

            step += fStackToStack(record, Action.RToM, Action.MToR, mS, rS, mid, right);

            step += fStackToStack(record, Action.MToR, Action.RToM, rS, mS, right, mid);

        }

        return step;

    }



    public static int fStackToStack(Action[] record, Action preNoAct,

                                    Action nowAct, Stack<Integer> fStack, Stack<Integer> tStack,

                                    String from, String to){

        if(record[0] != preNoAct && fStack.peek() < tStack.peek()){

            tStack.push(fStack.pop());

            System.out.print("Move" + tStack.peek() + "from" + from + "to" + to);

            record[0] = nowAct;

            return 1;

        }

        return 0;

    }



    public static void main(String[] args) {

        System.out.println("Hello World!");

    }

}



参考资料:《程序员代码面试指南》左程云 著

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值