【递归】Hanoi双塔问题,如何去找状态方程

引言


汉诺塔问题是计算机科学中经典的问题之一,也是计算机科学入门课程中常见的问题。汉诺塔问题的解法可以让我们了解到递归算法的实现方法,也可以帮助我们深入理解递归算法的本质。在本文中,我们将介绍汉诺塔问题的定义和解法,并给出具体的实现过程以及测试案例。

问题描述


【题目】给定A,B,C三根足够长的细柱,在A柱上放有n个中间有空的圆盘,共有n个不同的尺寸。现要将 这些国盘移到C柱上,在移动过程中可放在B柱上暂存。要求:
(1)每次只能移动一个圆盘;
(2) A、B、C三根细柱上的圆盘都要保持上小下大的顺序;
任务:设An为n个圆盘完成上述任务所需的最少移动次数,对于输入的n,输出An。
【示例】
输入:3
输出:7
在这里插入图片描述
汉诺塔问题是一个经典的数学问题,它由三根柱子和N个圆盘组成,其中圆盘的尺寸不同,且初始时按从小到大的顺序依次排列在第一根柱子上。现在要求将所有的圆盘都移到第三根柱子上,并且每次只能移动一个圆盘,且在移动过程中必须保证每根柱子上的圆盘仍然保持上小下大的顺序。请问如何移动才能使得所有圆盘都移到第三根柱子上,且符合要求?|

解析

我们先看看当圆盘数由1到3的情况:
在这里插入图片描述
我们就可以得到以下数据:

圆盘数 移动次数
1 1
2 3
3 7
  • 具体来说,我们可以将第一个柱子上的n-1个圆盘移动到第二个柱子,再将第一个柱子上的最后一个圆盘移动到第三个柱子,最后再将第二个柱子上的n-1个圆盘移动到第三个柱子上。在移动的过程中,我们需要保证每根柱子上的圆盘仍然按照上小下大的顺序排列。这里很明显可以得到状态方程:
    F ( n ) = 2 F ( n − 1 ) + 1 F(n)=2F(n-1)+1 F(n)=2F(n1)+1
    在这里插入图片描述

实现过程

  • 在实现过程中,我们需要定义一个递归函数来实现汉诺塔问题的解法。下面是递归函数的伪代码:
hanoi(n, a, b, c):
    if n == 1:
        move a to c
    else:
        hanoi(n-1, a, c, b)
        move a to c
        hanoi(n-1, b, a, c)

其中,n表示当前要移动的圆盘数量,a、b、c分别表示三根柱子的名称,move a to c表示将柱子a上的一个圆盘移动到柱子c上。在递归的过程中,我们需要将上面的n-1个圆盘从a柱移动到b柱,再将最底下的一个圆盘从a柱移动到c柱,最后将b柱上的n-1个圆盘移动到c柱。

递归题解

int hanoi(int n, char a, char b, char c) {
    
    
    if (n == 1) {
    
    
        return 1;
    } else {
    
    
        int count = hanoi(n-1, a, c, b);
        count++;
        count += hanoi(n-1, b, a, c);
        return count;
    }
}

当然也可以根据状态方程遍历

//计算2*(2^n-1)的值。
 
#include <stdio.h>
 
int main() {
    
    
    int n = 0;//圆盘的个数,说到底就是你想算2的多少次幂
    scanf("%d", &n);
    int bigNumber[n];//用于存储超级大数,当然这里的n你可以写成100、1000、10000都行
 
    //先把数组初始化元素全部设为0
    for (int i = 0; i < n; ++i) {
    
    
        bigNumber[i] = 0;
    }
 
    //开始计算2^n,注意这里仅仅是计算2^n、2^n、2^n,还没到减1的那一步
    //注意,这里是将数组“颠倒”使用的,bigNumber[0]表示大数的最后一位(个位数字),bigNumber[n]表示大数的最高位,至于原因,看下去就知道了
     
     
    bigNumber[0] = 1;//第一位等于1,以便进行乘积运算,要不然0^n结果全部都是0
    for (int i = 0; i < n; ++i) {
    
    //这里的n表示乘积次数,你想求2的多少次幂,这里就写几
        for (int j = 0; j < n; ++j) {
    
    
            bigNumber[j] *= 2;//每次遍历,数组中的每一位都要乘2。例如计算123*2时,1、2、3都要乘2,这里原理一样
        }
         
        //乘法做完了,现在开始挨个位检查,看看哪位数值超过了9
        for (int k = 0; k < n; ++k) {
    
    
            if (bigNumber[k] > 9) {
    
    
                bigNumber[k+1]++;//如果某一位数超过了9,则需要进位
                bigNumber[k] = bigNumber[k]%10;//进位后自身取余,例如13%10=3
            }
        }
    }
 
     
     
    //现在开始执行2^n-1操作
    bigNumber[0]-= 1;//放心减1,因为2^n个位数绝对只能是2、4、8、6...
 
     
     
    //现在开始执行2*(2^n-1),说白了也就是将大数整体再乘一次2,原理同上方的次幂完全相同,只不过部分位置的循环条件发生变化
     
    for (int i = 0; i < 1; ++i) {
    
     //这里的i从n变为1,就变了这一个地方,因为只进行一次乘2操作,其余的啥都没变
         
        for (int j = 0; j < n; ++j) {
    
    
            bigNumber[j] *= 2;//每次遍历,数组中的每一位都要乘2,例如123*2时1、2、3都要乘2
        }
         
        //乘法做完了,现在开始挨个位检查,看看哪位数值超过了9
        for (int k = 0; k < n; ++k) {
    
    
            if (bigNumber[k] > 9) {
    
    
                bigNumber[k+1]++;//如果某一位数超过了9,则需要进位
                bigNumber[k] = bigNumber[k]%10;//进位后自身取余, 例如13%10=3
            }
        }
    }
 
 
    //好了,结果已经计算完毕,现在就是输出结果
    for (int i = n-1; i >= 0 ; --i) {
    
    
        //数组中可能出现一些位为0,没用上,那就把这些0找出来,例如123450000000000,后面的那一堆0要他何用?不输出它
        if (bigNumber[i] > 0) {
    
    
         
            //找到了第一个不为0的位,也就是大数的“最高位”,注意,这里是将数组“颠倒”使用的,bigNumber[0]表示大数的最后一位(个位数字),
            //bigNumber[n]表示大数的最高位,至于原因,看下去就知道了
            for (int j = i; j >= 0; --j) {
    
      //"倒“着输出数组的每一位即可
                printf("%d", bigNumber[j]);
            }
            break;
        }
    }
    return 0;
}

如果你想输出每个步骤,这里由一段简单的示例:

#include <stdio.h>

int hanoi(int n, char a, char b, char c) {
    
    
    if (n == 1) {
    
    
        printf("将第%d个盘子从%c移动到%c\n", n, a, c);
        return 1;
    } else {
    
    
        int count = hanoi(n-1, a, c, b);
        printf("将第%d个盘子从%c移动到%c\n", n, a, c);
        count++;
        count += hanoi(n-1, b, a, c);
        return count;
    }
}

int main() {
    
    
    int n;
    printf("请输入盘子的数量:");
    scanf("%d", &n);
    int count = hanoi(n, 'A', 'B', 'C');
    printf("总共需要移动的次数: %d\n", count);
    return 0;
}

请输入盘子的数量:3
将第1个盘子从A移动到C
将第2个盘子从A移动到B
将第1个盘子从C移动到B
将第3个盘子从A移动到C
将第1个盘子从B移动到A
将第2个盘子从B移动到C
将第1个盘子从A移动到C
总共需要移动的次数: 7

猜你喜欢

转载自blog.csdn.net/m0_73589720/article/details/130048895