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

漢諾塔遞迴演算法理解及實現

漢諾塔:(Hanoi)是一種玩具,如圖:
這裡寫圖片描述
從左到右 A B C 柱 大盤子在下, 小盤子在上, 藉助B柱將所有盤子從A柱移動到C柱, 期間只有一個原則: 大盤子只能在小盤子的下面.
問題理解與描述:
1.問題的理解與描述
問題的形式化表示為:
輸入:圓盤數n,3根細杆—— 源杆A、過渡杆B和目標杆C。
輸出:圓盤從源杆移動到目標杆過程的最少步驟序列。
2.演算法的虛擬碼:

HANOI(n, A, B, C)
1 if n=1
2   then print A,"", C
3       return
4 HANOI(n-1,A, C, B)
5 print A,""
, C 6 HANOI(n-1,B, A, C)

3.演算法的執行時間:
對於盤數n,HANOI過程的執行時間
這裡寫圖片描述

案例 1 - 假設只有一個盤子的時候, 盤子數量 N=1
只有一個步驟 將第1個盤子從A移動到C, 為了對比方便我這樣來描述這個步驟:
步驟 盤子編號 從柱子移動 移動到柱子
1 1 A C
案例 2 - 如果有兩個盤子, 盤子數量 N = 2
步驟 盤子編號 從柱子移動 移動到柱子
1 1 A B
2 2 A C
3 1 B C
案例 3 - 如果有三個盤子, 盤子數量 N = 3
步驟 盤子編號 從柱子移動 移動到柱子
1 1    A C
2 2    A     B
3 1 C B
4 3 A C
5 1 B A
6 2 B C
7 1 A C
如何找出盤子移動的規律 ?
我們要做的最重要的一件事情就是永遠要把最底下的一個盤子從 A 移動到 C
看看上面從1個盤子的移動到3個盤子的移動, 在移動記錄中,當盤子的編號和盤子數量相同的時候他們的步驟都是從A移動到C (看加粗的部分),其它的步驟對等.
再觀察第3個案例中的第 1-3 步 和 第 5-7步
第 1-3 步 目的是從 A 移動到 B 如果我們把 B 當作終點, 那麼這裡的第 1-3 步理解起來和 第2個案例的三個步驟完全相同, 都是通過一個柱子來移動,和第2個案例比起來在後面加括號來表示
1 1    A C ( A -> B)
2 2    A  B ( A -> C)
3 1 C B ( B -> C)
總結:將盤子B變成C即可.
第 5-7 步 目的是從 B 移動到 C 如果我們把 C 當作終點, 那麼這裡的 5-7 步理解起來和上面也是一樣的, 和第2個案例的三個步驟也完全相同.和第2個案例比起來就是:
5 1 B A ( A -> B)
6 2 B C ( A- > C)
7 1 A C ( B -> C)
總結: 將盤子B變成A即可
根據這個演示可以明確幾點規律:
1. 當盤子只有一個的時候,只有一個動作 從 A 移動到 C 即結束.
2. 當有N個盤子的時候, 中間的動作都是從 A 移動到 C, 那麼表示最下面的第N個盤子移動完畢
3. 中間動作之上都可以認為是: 從 A 移動到 B
4. 中間動作之下都可以認為是: 從 B 移動到 C
2,3,4 可以表示為
1 1 A B
2 2 A C
3 1 B C
理解二:(參考:

http://blog.csdn.net/leo115/article/details/7991734
美國的一位學者發現一種出人意料的簡單的演算法,只要輪流兩步操作既可以實現:首先,把三張桌子按順序首尾相接的排列,形成一個環,然後對A上的盤子開始移動,順時針擺放成 A B C的順序:
若n為奇數,圓盤的移動順序是 A->C->B->A->C->B->A……… 即 間隔兩個步長移動 。此處的n代表盤子位的層數,比如說 3 層漢諾塔就是從下往上數第1、3 個盤子移動的順序。
若n為偶數,圓盤移動的順序為A->B->C->A->B->C->A……….即 間隔一個步長移動。對n的解釋同上 第二個盤子移動 A->B->C。
5.程式碼實現(c):

/*************hanoi.c********************/
#include<stdlib.h>
#include <stdio.h>
#include "hanoi.h"
/*************找x杆頂部盤的編號**********
輸入引數:current[i]記錄第i號盤所在的杆號
          x;杆的編號

輸出引數:x杆頂部盤的編號
****************************************/

int pickTopDisk(char* current,char x)
{
    int i = 0;
    while (current[i] != x)
        i++;
    return i;
}
/*************漢諾塔**********
輸入引數:current[i]記錄第i號盤所在的杆號
          n:盤的數量
          A,B,C:杆的編號
****************************************/
void hanoi(char* current, int n, char A, char B, char C)
{
    static int cout = 0;  //static型別變數會在函式多次呼叫時儲存改變的值,並且初始化操作僅做一次
    int i = 0;
    if (n==1)
    {
        i = pickTopDisk(current, A);
        current[i] = C;
        cout++;
        printf("move %d disk %d: %c->%c\n", cout, i + 1, A, C);
        return;
    }
    hanoi(current, n - 1, A, C, B);
    current[n- 1] = C;
    cout++;
    printf("move %d disk %d: %c->%c\n", cout, n, A, C);
    hanoi(current, n - 1, B, A, C);
}
/****************hanio.h************/
#ifndef _HANOI_H
#define _HANOI_H

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

    int pickTopDisk(char* current, char x);
    void hanoi(char* current, int n, char A, char B, char C);
#ifdef __cplusplus
}
#endif
#endif
/**************main.c************************/
#include <stdlib.h>
#include "hanoi.h"

int main(int argc, char** argv)
{
    char current[] = { 'A', 'A', 'A', 'A' };
    char A = 'A', B = 'B', C = 'C';
    hanoi(current, 4, A, B, C);
    system("pause");
    return EXIT_SUCCESS;
}

6執行結果:
這裡寫圖片描述

參考:《演算法設計、分析與實現:C、C++和Java》 徐子珊