草庐IT

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

吴NDIR 2023-09-27 原文

引言


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

问题描述


【题目】给定A,B,C三根足够长的细柱,在A柱上放有n个中间有空的圆盘,共有n个不同的尺寸。现要将 这些国盘移到C柱上,在移动过程中可放在B柱上暂存。要求:
(1)每次只能移动一个圆盘;
(2) A、B、C三根细柱上的圆盘都要保持上小下大的顺序;
任务:设An为n个圆盘完成上述任务所需的最少移动次数,对于输入的n,输出An。
【示例】
输入:3
输出:7

汉诺塔问题是一个经典的数学问题,它由三根柱子和N个圆盘组成,其中圆盘的尺寸不同,且初始时按从小到大的顺序依次排列在第一根柱子上。现在要求将所有的圆盘都移到第三根柱子上,并且每次只能移动一个圆盘,且在移动过程中必须保证每根柱子上的圆盘仍然保持上小下大的顺序。请问如何移动才能使得所有圆盘都移到第三根柱子上,且符合要求?|

解析

我们先看看当圆盘数由1到3的情况:

我们就可以得到以下数据:

圆盘数移动次数
11
23
37
  • 具体来说,我们可以将第一个柱子上的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

有关【递归】Hanoi双塔问题,如何去找状态方程的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  3. ruby - 在 Ruby 程序执行时阻止 Windows 7 PC 进入休眠状态 - 2

    我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0

  4. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  5. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

    给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

  6. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  7. ruby - 在 64 位 Snow Leopard 上使用 rvm、postgres 9.0、ruby 1.9.2-p136 安装 pg gem 时出现问题 - 2

    我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po

  8. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  9. ruby - 通过 rvm 升级 ruby​​gems 的问题 - 2

    尝试通过RVM将RubyGems升级到版本1.8.10并出现此错误:$rvmrubygemslatestRemovingoldRubygemsfiles...Installingrubygems-1.8.10forruby-1.9.2-p180...ERROR:Errorrunning'GEM_PATH="/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/ruby-1.9.2-p180@global:/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/rub

  10. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

随机推荐