1. 程式人生 > >漢諾塔的遞迴實現演算法詳解

漢諾塔的遞迴實現演算法詳解

這裡我們再詳細地介紹一下漢諾塔的移動原理,假設三根柱子分別是 A、B、C,一開始 A 上有 N 個圓盤,從小到大、從上到下分別是 1、2……N-1、N,我們要把 A 上的 N 個圓盤全部移動到 C 上面,且每次只能移動每根柱子最上面的一個圓盤。

我們可以反過來考慮一下,若要把 A 上的圓盤全部移動到 C 上,那麼需要把前 N-1 個圓盤按順序落在 B 上了,最後的第 N 個圓盤才可以直接從 A 移動到 C 上,從而完成第 N 個圓盤的移動。之後把 B 上的 N-2 個圓盤移動到 A 上,再把 B 上剩下的最後一個圓盤,也就是第 N-1 個圓盤直接移動到 C 上,這時就已經完成了最下面兩個圓盤的移動。繼續這樣的移動,直到完成遊戲。

經過上述對漢諾塔移動的詳細分析,大家可以先思考和嘗試一下如何寫這個演算法的實現程式碼。

漢諾塔的遞迴實現

上面的步驟在程式碼上使用遞迴是最好實現的。要使用遞迴,就要考慮到需要進行遞迴的是哪部分。我們先看一下程式碼,然後結合程式碼具體分析這樣寫的原因。
package me.irfen.algorithm.ch02.extend;
public class HanoiTest {
    public static void hanoi(int n, char A, char B, char C) {
        if (n == 1) {
            // 只有一個圓盤需要移動的時候移動完結束
            move(A, C);
            return;
        }
        // 先把A上的n-1個圓盤移動到B上
        hanoi(n - 1, A, C, B);
        // 把A上最後一個圓盤移動到C上
        move(A, C);
        // 接下來遞迴,把B上的n-1個圓盤移動到C上
        hanoi(n - 1, B, A, C);
    }

    /**
     * 把A最上面的圓盤移動到C上去
     * @param A
     * @param C
     */
    private static void move(char A, char C) {
        System.out.println(A + "-->" + C);
    }

    public static void main(String[] args) {
        hanoi(3, 'A', 'B', 'C');
    }
}
現在有了漢諾塔遞迴實現的具體程式碼,我們來分析一下。

hanoi 函式的第 1 個引數是柱子上需要移動的圓盤的個數,後三個引數分別為三根柱子的標識。首先當 n 為 1 時,需要移動的圓盤只有一個,直接把 A 上的圓盤移動到 C 上就可以了,同時代碼結束,因為已經沒有需要移動的圓盤了。

接下來是漢諾塔實現的關鍵,即把 A 上所有的圓盤移動到 C 上,需要先把 A 最上面的 n-1 個圓盤移動到 B 上,於是有了“hanoi(n-1,A,C,B);”這樣一行遞迴呼叫,接下來只需要把 A 上最後剩下的最大的圓盤移動到 C 上。

現在 B 上有 n-1 個圓盤,C 上有一個最大的圓盤,接下來把 B 上這 n-1 個圓盤也移動到 C 上。此時把 B 想象成之前的 A,有一堆待移動的圓盤;把 A 想象成之前的 B,是空的柱子,這時我們只需要把呼叫方式變為“hanoi(n-1,B,A,C);”,就可以完成移動,這就是遞迴呼叫的思想所在了。

在遞迴呼叫中會繼續執行該方法,改變引數的值,繼續列印,這樣每次呼叫會減少 n 的值,直到 n 最後變為 1 之後,結束遞迴呼叫,最終完成漢諾塔移動。

轉變一下思想,就能夠理解從 B 上移動 n-1 個圓盤到 C 上其實和從 A 上移動圓盤到 C 上一樣,我們會發現漢諾塔的遞迴呼叫非常簡單。