zoukankan      html  css  js  c++  java
  • 初识汉诺塔问题

    汉诺塔

    汉诺塔(Tower of Hanoi)源于印度传说中,大梵天创造世界时造了三根金钢石柱子,其中一根柱子自底向上叠着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。

    ——引自维基百科

    若给汉诺塔传说中三根柱子分别用英文字母a,b,c命名,其中只有a柱子摆放n片圆盘(1<=n<=100000), 若要把a柱子上的所有圆盘转移到c柱子上,问最少需要移动多少次圆盘。

    移动圆盘的规则如下:

    1. 每次只能移动一片圆盘
    2. 直径大的圆盘必须摆放在直径小的圆盘之上

    递归求解

    汉诺塔问题通过简单的递归进行求解,代码比较简洁,通俗易懂。其实汉诺塔问题的移动次数是有规律可寻的,通过递归代码找出相应的规律,并通过数学方法得到结果效率才是最高的。

    • 当n=1时,a柱子只有一个圆盘,直接移至c柱
    • 当n>1时,根据规则1和2,将a柱子n-1个圆盘移动到b柱子,然后将a剩下的一个圆盘移动到c,接着再把b上暂时放着的n-1个圆盘移动到c

    递归求解其实就是不断降低问题规模的过程,将b柱子的n-1个圆盘移至c何尝不重复上述两点的过程。递归求解的C代码如图3-1所示。

    void Hanoi(int n, char a, char b, char c)
    {
        if(n == 1)
        {
            Move(a, c);
        }
        else
        {
            Hanoi(n-1, a, c, b); /*将a柱子n-1个圆盘移动到b柱子*/
            Move(a, c);      /*将a剩下的一个圆盘移动到c*/
            Hanoi(n-1, b, a, c); /*再把b上暂时放着的n-1个圆盘移动到c*/
        }
    }
    
    void Move(char a, char b)
    {
        printf("Move 1 disk: %c ---------> %c
    ", a, b);
    }

    图3-1    汉诺塔递归求解代码

     

    如图3-1的代码可以得出汉诺塔移动圆盘次数的递推关系:

    • Hanoi(n) = 1 , n =1 ;
    • Hanoi(n) = 2 * Hanoi(n-1) + 1, n>1;

    令b(n) = Hanoi(n)+1,可以得出b(n)是一个以2为底,公比为2的等比数列。由此可得,

    b(n) = 2n, n>0

    Hanoi(n) = 2n - 1, n>0


    增加约束条件的汉诺塔

    Q: 若加上一个限制条件,圆盘只能在相邻柱子之间移动,又如何解决?假设a, b, c并排,b在中间,即a, c不相邻,把a上的一个圆盘移动到c上必须先移至b,然后再移动到c。

    From TOJ 3270 Strange Hanoi Tower

     同理使用递归求解,根据原有规则和新增约束条件,推导出移动圆盘的次数的规律。

    • 当n=1时,a柱子只有一个圆盘,先移到b,再移至c
    • 当n>1时,先将a柱的n-1个圆盘通过b柱移至c柱,再将a柱子剩下的一个圆盘移到b; 接着把c柱n-1个圆盘通过b柱移到a; 然后把b柱子目前唯一的圆盘移至c,最后把a柱子的n-1个圆盘通过b移至c

    递归代码如图3-2所示。

    void Hanoi(int n, char a, char b, char c)
    {
        if(n==1)
        {              //a柱子只有一个圆盘,先移到b,再移至c
            Move(a, b);
            Move(b, c);
        }
        else
        {
            Hanoi(n-1, a, b, c); //先将a柱的n-1个圆盘通过b柱移至c柱
            Move(a, b);          //a柱子剩下的一个圆盘移到b
            Hanoi(n-1, c, b, a); //把c柱n-1个圆盘通过b柱移到a
            Move(b, c);          //把b柱子目前唯一的圆盘移至c
            Hanoi(n-1, a, b ,c); //把a柱子的n-1个圆盘通过b移至c
        }
    }
    void Move(char a, char b)
    {
            printf("%c --> %c
    ", a, b);
    }

     图3-2    增加约束条件的汉诺塔递归代码

    由图3-2可得,移动圆盘次数与圆盘个数的递推关系,

    • 当n=1时,Hanoi(n) = 2;
    • 当n>1时,Hanoi(n) = 3 * Hanoi(n-1) + 2;

    最后求得:

    Hanoi(n) = 3n - 1, n>0

     这道题目的结果可能会很大,输出的结果要 mod 1,000,000,007。

    高阶整数幂取模

     因此TOJ 3270这道题目转化为求 (3n - 1) % 1000000007的数学问题。对高阶整数幂取模运算,可以通过除幂处理。题目的代码如图3-3所示。

    #include <stdio.h>
    const long long p = 1000000007;
    
    long long power(long long x, long long n)
    {//
        if( n == 0)
            return 1;
        else if(n == 1)
            return x;
        else
        {
            long long tmp = power(x, n/2);
            if( n % 2 == 1)
                return tmp * tmp * x % p;  // 若 n 为奇,3n = 3 * 3n/2 * 3n/2
            else
                return tmp * tmp % p;      // 若 n 为偶,3n = 3n/2 * 3n/2
        }
    }
    
    int main()
    {
        long long n;
        while(scanf("%lld", &n) && n)
        {
            printf("%lld
    ", power(3, n)-1);
        }
        return 0;
    }

    图3-3    TOJ 3270 Strange Hanoi Tower的参考代码

    思考:为什么得到的结果要取模?

    任何一个质数总能除尽任何几何级数中的某一项减1,且该项的指数是这个给定的质数减1的因子。

    —— 费马

    有兴趣的读者可以阅读神奇的费马小定理这篇文章。

  • 相关阅读:
    Taro文件上传:Blob Url下载Blob对象本身并通过接口上传到服务器
    Taro项目遇到的问题
    RPC是什么?
    句柄
    正向代理和反向代理
    Go Micro搭建简单微服务
    gRPC奇怪的编译命令protoc
    官网下载Git方法
    Go/golang:解决依赖包模块安装问题
    URL
  • 原文地址:https://www.cnblogs.com/vsky/p/5014657.html
Copyright © 2011-2022 走看看