化繁為簡 經(jīng)典的漢諾塔遞歸問題 in Java

問題描述
在世界中心貝拿勒斯(在印度北部)的圣廟里,一塊黃銅板上插著三根寶石針奈辰。印度教的主神梵天在創(chuàng)造世界的時候栏妖,在其中一根針上從下到上地穿好了由大到小的64片金片,這就是所謂的漢諾塔奖恰。不論白天黑夜吊趾,總有一個僧侶在按照下面的法則移動這些金片:一次只移動一片,不管在哪根針上瑟啃,小片必須在大片上面论泛。僧侶們預(yù)言,當所有的金片都從梵天穿好的那根針上移到另外一根針上時蛹屿,世界就將在一聲霹靂中消滅屁奏,而梵塔、廟宇和眾生也都將同歸于盡错负。
扯遠了坟瓢,把這個問題簡單描述下有A,B,C三根柱子勇边,將A柱上N個從小到大疊放的盤子移動到C柱,一次只能移動一個折联,不重復(fù)移動粒褒,小盤子必須在大盤子上面。
問一共需要移動多少次,步驟是什么?



解決思路
讓我們從簡單的情況下開始考慮
首先我說明幾個數(shù)學(xué)符號诚镰,我自己瞎編的奕坟,為了方便表示問題,不用打那么多字
A -> B
箭頭意思代表是從 A柱 移動到 B柱 ,每次移動都是移動最上面的那一塊

n = 1
只有一個盤子的時候清笨,很簡單执赡,直接移就是了,用數(shù)學(xué)符號記錄一下代表 A -> C

n = 2
如果有兩個盤子,就分為三步
1.先將最上面的盤子也就是(從下往上數(shù))第2個盤子,先移動到B 記為 A -> B
2.然后將第一個盤子移動移動到C , A -> C
3.最后將 B柱子上的盤子 移動到 C柱 , B -> C

n = 3
如果有三個盤子,就分為7步,這里就不打字了函筋,太累沙合,用數(shù)學(xué)符號表示

A -> C

A -> B

C -> B

A -> C

B -> A

B -> C

A -> C



額,不要擺出一副黑人問號臉...跌帐,自己隨便拿三個道具擺一擺就知道了
當你擺一擺的時候就知道首懈,我知道了,一個盤子移動1次谨敛,二個盤子移動3次究履,三個盤子移動7次,四個盤子移動15次脸狸,N個盤子移動 (2^n - 1) 次!
恭喜你答對了最仑!數(shù)學(xué)歸納法找規(guī)律不需多久就可以找到規(guī)律,但還有問題是究竟要怎么移呢?... 以及用程序怎么解決呢炊甲?
n = n....
抽象出這個問題
將問題抽象出來并且轉(zhuǎn)化為數(shù)學(xué)模型或者公式泥彤,是解決現(xiàn)實生活中復(fù)雜問題的一個很好的解決辦法,例如谷歌的翻譯卿啡,大家都覺得很智能吟吝,自然語言的翻譯從20世界60年代就開始研究了,具體細節(jié)這里不是重點颈娜,最后的解決思路是基于統(tǒng)計模型來解決的剑逃,也就是說,困擾了很多年的問題最后是抽象成一個概率論公式得以解決官辽,事實上蛹磺,眾多復(fù)雜的問題最后進行抽象其實就是幾個流程和公式而已,下面就以這個哈諾塔問題為例同仆。

當有N個盤子的時候萤捆,似乎很難去想具體怎么實現(xiàn),既然這么難想,就不用去想鳖轰,首先這個問題清酥,中有三個柱子,A,B,C蕴侣,這里也太具體了焰轻,抽象一下,怎么抽象呢昆雀?假如這個問題只有2根柱子辱志,你能完成嗎?廢話狞膘,肯定不行啊揩懒,我還需要一個柱子來輔助移動,所以這里的A,B,C三個柱子就抽象成挽封,起始柱已球,中間柱,目標柱辅愿,這里用from,mid,to來表示

ok智亮,現(xiàn)在是盤子的數(shù)量抽象成了n,柱子也抽象成了,from,mid,to 三種柱子点待,下面對過程進行抽象阔蛉,將n個盤子從 from 柱子 移動到 to 柱 ,其實總體來看是三步

將n-1個盤子從 起始 柱 移動到 中間 柱
將第n個盤子從 起始 柱 移動到 目標 柱
將第n-1個盤子從 中間 柱 移動到 目標 柱 這里你可能會說了癞埠,我靠状原,不是只讓移動一個盤子的嗎,你這第1步和第3步移動了n-1個盤子啊...苗踪,對颠区,所以我這里的過程說的是抽象的過程,也就是說不管具體的實現(xiàn)細節(jié)是怎樣的徒探,要達成所有的盤子都從A->C的效果瓦呼,中間一定是有一步是達到這個效果的喂窟,就好比你從北京去紐約测暗,假設(shè)只有一條國際航班,要經(jīng)過巴黎磨澡,那我就可以說你從北京去紐約碗啄,只有兩步,第一步是去巴黎稳摄,第二步是從巴黎去紐約稚字,這里的道理是同樣的。

那么現(xiàn)在將上面的第1步怎么實現(xiàn)呢?同樣抽象胆描,只要將n 替換成 n-1 即可,第三步也是同理

將n-2個盤子從 起始 柱 移動到 中間 柱
將第n-1個盤子從 起始 柱 移動到 目標 柱
將第n-2個盤子從 中間 柱 移動到 目標 柱

代碼實現(xiàn)
那這個過程瘫想,用程序來抽象就是 一個Plate方法,接受四個參數(shù),n,from,mid,to,四個參數(shù)的意思分別是
n 代表要移動幾個盤子
from 代表起始柱的名字
mid 代表借助的中間柱的名字
to 代表目標柱的名字

方法的 作用是 將 n 個盤子 從 from 移動到 to
代碼如下

/**
     * 將n個盤子從 from 移動到 to
     */
    public static void movePlate(int n, String from, String mid, String to) {
        /* 如果只有一個盤子,就直接從 from 移動到 to */
        if (n <= 1) {
            System.out.println(from + " -> " + to);
            return;
        }

        /* 1.將 n-1 個盤子 從 from 移動到 mid */
        movePlate(n - 1, from, to, mid);

        /* 2.將第 n 個盤子 從 from 移動到 to */
        System.out.println(from + " -> " + to);

        /* 3.將 n-1 個盤子 從mid 移動到 to */
        movePlate(n - 1, mid, from, to);
    }

你可以發(fā)現(xiàn)除去注釋昌讲,真正的代碼只有5行,就將這個問題給解決了国夜,再次提醒這里的from , mid ,to 是形參,代表的是起始住短绸,中間柱车吹,和目標駐,不是具體的哪一個柱子醋闭,所以在第12行窄驹,因為第一步是將N-1個移動到中間柱,所以參數(shù)時from,to,mid,第18行將n-1個從中間柱移動到目標駐,所以參數(shù)時mid,from,to,中間的參數(shù)就是需要借助的柱子证逻。

下面測試一下代碼,這里根據(jù)題目把from,mid,to起個名分別是A,B,C,那執(zhí)行這個方法就是將3個盤子從A移動到C

public static void main(String[] args) {
        movePlate(3, "A", "B", "C");
    }

n = 3 的時候

A -> C
A -> B
C -> B
A -> C
B -> A
B -> C
A -> C

Process finished with exit code 0

發(fā)現(xiàn)和上面人為思考的結(jié)果是一樣的哦

當n = 4的時候

A -> B
A -> C
B -> C
A -> B
C -> A
C -> B
A -> B
A -> C
B -> C
B -> A
C -> A
B -> C
A -> B
A -> C
B -> C

Process finished with exit code 0

一共是15步,也沒有問題,再多的我就不測了,有興趣的自己試試按照上面的打印結(jié)果來進行操作

推算次數(shù)

利用遞歸的方法同樣可以很容易的寫出計算次數(shù)的方法

public int countMovePlate(int n) {
        if (n <= 1) return 1;
        return countMovePlate(n - 1) + 1 +countMovePlate(n-1);
    }

那問題來了,還能優(yōu)化嗎乐埠?

上文說到人為觀察,利用數(shù)學(xué)歸納法可以得出需要的次數(shù)是 (2^n - 1) 次囚企,那么這個數(shù)究竟是怎么得到呢饮戳?
先把上面的程序復(fù)制下來,進行觀察

/* 1.將 n-1 個盤子 從 from 移動到 mid */
        movePlate(n - 1, from, to, mid);

        /* 2.將第 n 個盤子 從 from 移動到 to */
        System.out.println(from + " -> " + to);

        /* 3.將 n-1 個盤子 從mid 移動到 to */
        movePlate(n - 1, mid, from, to);

核心代碼就三行,假設(shè)moveplate這個方法需要移動的次數(shù)為 (a_n) 次,那么上面的這三行需要移動的次數(shù)就應(yīng)該是 [ a_n = a_{n-1} + 1 + a_{n-1} ]

第一步是 (a_n) ,第二步是固定的1次,第三步又是 (a_n) ,然后當 n = 1的時候 (a_1 = 1) 洞拨,再總結(jié)整理一下就成了

[ a_n=\left{ \begin{aligned} 1, n = 1\ 2a_{n-1} + 1,n > 1 \end{aligned} \right. ]

有沒有夢回高中的趕腳扯罐,這是一個很簡單的變形等比數(shù)列,我們讓兩邊都加上1

[ a_n + 1 = 2a_{n-1} + 1 + 1 ]

也就是

[ a_n + 1 = 2a_{n-1} + 2 ]

再提取一下

[ a_n + 1 = 2(a_{n-1} + 1) ]

兩邊都除以 ((a_{n-1} + 1))

于是就成了

[ \frac {a_n + 1}{a_{n-1} + 1} = 2 ]

那接著將這個公式一直寫豎式將他們相乘

[ \frac {a_n + 1} {a_{n-1} + 1} = 2 ]

[\frac {a_{n-1} + 1}{a_{n-2} + 1} = 2 ]

[\frac {a_{n-2} + 1}{a_{n-3} + 1} = 2 ]

[\vdots]

[\frac {a_2 + 1}{a_1 + 1} = 2 ]

接著約分, Markdown LaTex公式的刪除線找了半天都沒找到烦衣,知道的麻煩告知一下.

約分結(jié)果是

[\frac {a_n + 1}{a_1 + 1} = 2^{n-1} ]

接著將前面的 (a_1 = 1) 代入,于是

[\frac {a_n + 1}{2} = 2^{n-1} ]

再整理一下

[a_n = 2^n - 1 , n > 1]

所以說

[ a_n=\left{ \begin{aligned} 1, n = 1\ 2^n - 1 , n > 1 \end{aligned} \right. ]

將n =1 代入 n > 1 的情況,也是成立的,因此

[a_n = 2^n - 1 ]

所以經(jīng)過推導(dǎo)之后java代碼如下,因為涉及到 (2^n) 這種運算,可以使用移位符,這樣底層移動速度很快

代碼如下

public static int countMovePlate(int n) {
        return n >= 1 ? (1 << n) - 1 : 0;
    }

結(jié)論

最終我們解決漢諾塔的移動順序與統(tǒng)計次數(shù)的代碼如下,可以看出并不需要幾行代碼就解決了問題

/* 打印出移動順序 */
    public static void movePlate(int n, String from, String mid, String to) {
        if (n <= 1) {
            System.out.println(from + " -> " + to);
            return;
        }
        movePlate(n - 1, from, to, mid);
        System.out.println(from + " -> " + to);
        movePlate(n - 1, mid, from, to);
    }
    
    /* 返回需要移動的次數(shù) */
    public static int countMovePlate(int n) { return n >= 1 ? (1 << n) - 1 : 0;}

java學(xué)習(xí)交流群:669823128 禁止閑聊歹河,非喜勿加

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市花吟,隨后出現(xiàn)的幾起案子秸歧,更是在濱河造成了極大的恐慌,老刑警劉巖衅澈,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件键菱,死亡現(xiàn)場離奇詭異,居然都是意外死亡今布,警方通過查閱死者的電腦和手機经备,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來部默,“玉大人侵蒙,你說我怎么就攤上這事「吊澹” “怎么了纷闺?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵算凿,是天一觀的道長。 經(jīng)常有香客問我犁功,道長氓轰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任浸卦,我火速辦了婚禮戒努,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘镐躲。我一直安慰自己储玫,他們只是感情好,可當我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布萤皂。 她就那樣靜靜地躺著撒穷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪裆熙。 梳的紋絲不亂的頭發(fā)上端礼,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天,我揣著相機與錄音入录,去河邊找鬼蛤奥。 笑死,一個胖子當著我的面吹牛僚稿,可吹牛的內(nèi)容都是我干的凡桥。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼蚀同,長吁一口氣:“原來是場噩夢啊……” “哼缅刽!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蠢络,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤衰猛,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后刹孔,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體啡省,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年髓霞,在試婚紗的時候發(fā)現(xiàn)自己被綠了卦睹。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡酸茴,死狀恐怖分预,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情薪捍,我是刑警寧澤,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站酪穿,受9級特大地震影響凳干,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜被济,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一救赐、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧只磷,春花似錦经磅、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至元媚,卻和暖如春轧叽,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背刊棕。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工炭晒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人甥角。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓网严,卻偏偏與公主長得像,于是被迫代替她去往敵國和親嗤无。 傳聞我的和親對象是個殘疾皇子屿笼,可洞房花燭夜當晚...
    茶點故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內(nèi)容