汉诺塔问题

汉诺塔问题

汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具。梵天创造世界的时候做了三根柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。(以上介绍来自百度百科)

这是一个五层的汉诺塔示意gif图:

hanoi.gif

由图可知:一个五层的汉诺塔要由 源柱 完全移动到 目标柱 需要移动圆盘的次数是 31 次。

假如要让我们用程序模拟这个过程。应该怎样去做呢?

首先可以把每一个柱子具象化为数组,三个柱子就是三个数组。其实具象化为栈比较好, 因为柱子只能从最上端取出,也只能从最上端放入,但是我们主要讨论汉诺塔,不再做栈的实现,相应的,我们对数组进行规定,只进行pop和push操作,这样我们也能得到类似栈的效果。(实际上在JS中,如果不借助node-gyp用C++实现栈结构,用JS数组实现栈结构也是最简单的)

首先我们定义三根柱子:

// JavaScript
let a = []            // 假设a为源柱
let b = []            // 中间柱
let c = []            // 假设c为目标柱
let count = 0         // count用于计算总共移动的次数
# Python
a = []                # 假设a为源柱
b = []                # 中间柱
c = []                # 假设c为目标柱
count = 0             # count用于计算总共移动的次数

1.仅有一个圆盘需要被移动的情况

现在我们假设 a数组中存放着一个数 a = [1], 那么要把 a中的 一个大小为1的盘子移动到 c中,我们只需要直接做这个移动操作: a → c ,即一步即可完成汉诺塔, 进行移动后三个数组分别为:

  • a == [ ]
  • b == [ ]
  • c == [1]

相应的, 我们需要定义“移动”圆盘的代码实现:

// JavaScript
/**
 * @function 在两根柱子间移动一片圆盘
 * @param {需要移动的源数组} source 
 * @param {要移动到的目标数组} target 
 */
const move = (source, target) => {
  target.push(source.pop())             // 用目标数组push 源数组的pop值来抽象移动
  count ++                              // 每移动一次总移动次数count 自加一次
}
# Python
def move(source: list, target: list) -> None:    
    global count                      # 引用全局的count变量
    target.append(source.pop())       # 进行移动
    count = count + 1                 # 移动次数+1

则我们第一步的函数可以简单定义:

// JavaScript
const once = (one ,two, three) => {
  move(one, three)
}
once (a, b, c)  //直接调用
# Python
def once(one: list, two: list, three: list) -> None:
    move(one, three)
once(a, b, c)  # 直接调用

2.假设两层的汉诺塔要怎么做呢?

// JavaScript
const twice = (one, two, three) => {
  once(one, three, two)    // 首先将第一个最小的圆盘移动到第二根柱子
  move(one, three)          // 将剩下的最大的圆盘移动到第三根柱子
  once(two, one, three)   // 将第一次移动的圆盘移动到第三根柱子上
}
# Python
def twice(one: list, two: list, three: list) -> None:
    once(one, three, two)
    move(one, three)
    once(two, one, three)
  

两层的情况我们必须考虑的是,要保证大圆盘在小圆盘下,我们要借助一个中间柱子来暂时存放小的圆盘,然后移动大圆盘到目标的柱子,再将小圆盘移动到目标柱。

3.三层的情况

// JavaScript
const third = (one, two, three) => {
  twice(one, three, two)
  move(one, three)
  twice(two, one, three)
}
# Python
def third(one: list, two: list, three: list) -> None: 
    twice(one, three, two)
    move(one, three)
    twice(two, one, three)

到第三层,我们可以理解为,先将上面的两层移动到第二根柱子,这样我们才能把最大的底座移动到第三根柱子上,然后再将第二根柱子上的两块圆盘, 借助第一根柱子(此时把它看做中转柱)移动到第三根柱子。

4.四层的情况

// JavaScript
const fourth = (one, two, three) => {
  third(one, three, two)
  move(one, three) 
  third(two, one, three)
}
# Python
def fourth(one: list, two: list, three: list) -> None:
    third(one, three, two)
    move(one, three)
    third(two, one, three)

同理,我们的需要是,先把最下面的最大的圆盘移动到目标柱,为此,我们需要把上面的三层圆盘先移动到第二根柱子上,怎么移动呢?将第三根柱子暂时当做中间柱,将第二根柱子看做目标柱,其实就是调用第三次的方法!然后将最大的圆盘移动到第三根柱子上,接着把第二根柱子上的圆盘借助第一根柱子做中转柱 移动到第三根柱子上。

5.规律

发现规律了吗?其实我们几步都在做同样的事情!
那么就天然符合递归!

// JavaScript
const hanoi = (n, src, sav, tar) => {
  if (n == 1) {
    move(src, tar)
  } else {
    hanoi(n - 1, src, tar, sav)
    move(src, tar)
    hanoi(n - 1, sav, src, tar)
  }
}

# Python
def hanoi(n: int, src: list, sav: list, tar: list) -> None:
    if n == 1:
        move(src, tar)
    else:
        hanoi(n - 1, src, tar, sav)
        move(src, tar)
        hanoi(n - 1, sav, src, tar)

n用作递归的结束条件,其实n表示的就是问题规模,可以看到,每一次我们执行完hanoi,问题的规模都会缩小一次!
调用方法:

// 给a柱子添加圆盘,由大到小排列
for (let i = 4; i >= 0; i--) {
  a.push(i)
}

hanoi(a.length, a, b, c)  // 调用
# 给a柱子添加圆盘,由大到小排列
for index in reversed(range(0, 4)):
    a.append(index)

hanoi(len(a), a, b, c)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,992评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,212评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,535评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,197评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,310评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,383评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,409评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,191评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,621评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,910评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,084评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,763评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,403评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,083评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,318评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,946评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,967评论 2 351

推荐阅读更多精彩内容