[Python3 練習] 006 漢諾塔2 非遞歸解法


題目:漢諾塔 II

(1) 解決方式

  • 利用“二進制”

(2) 具體說明

  • 統一起見
    • 我把左、中、右三根柱子依次稱為 A 塔、B 塔、C 塔
    • 金片默認都在 A 塔
    • n 片金片從小到大依次編號為 0 號、1 號、……、n-1 號

1) 舉個“栗子”

  • 假設有一個 4 層高的漢諾塔,設初始值為 0000(2)
  • 按 "8"、"4"、"2"、"1" 稱呼二進制的各位
  • "8"位、"4"位、"2"位、"1"位依次對應 3 號金片、2 號金片、1號金片、0 號金片
  • 如圖



  • 開始累加,每次加 1
  • 0000(2) -> 0001(2)
  • "1"位由 0 變 1,則將 0 號金片右移,即將 0 號金片由 A 塔移至 B塔
    • 補充:若要將 C 塔上的金片右移,則移至 A 塔,三個塔是循環的
  • 如圖



  • 0001(2) -> 0010(2)
  • 產生進位;進到哪位,則移動該位對應的金片
  • 此時進位至"2"位,則右移 1 號金片
  • 右移 1 號金片時,因為 1 號金片不能放在 B 塔的 0 號金片上方,所以繼續向右走,C 塔正好符合要求
  • 如圖



  • 0010(2) -> 0011(2)
  • "1"位由 0 變 1,則將 B 塔的 0 號金片右移至 C 塔
  • 如圖



  • 0011(2) -> 0100(2)
  • 產生進位,此時進至“4”位,則將 A 塔上的 2 號金片右移至 B 塔
  • 如圖



  • 0100(2) -> 0101(2)
  • 個位由 0 變 1,則將 C 塔的 0 號金片右移至 A 塔
  • 如圖


……

  • 按這個方法進行下去,當數字變成 "1111" 時,A 塔的 4 片金片就都在 C 塔上了

2) 一些說明

  • 此“二進制”方法可以解決漢諾塔,但奇數金片與偶數金片在結果上有些許不同

    • 按照上面的規則,奇數金片最終會移至 B 塔,偶數金片最終會移至 C 塔
    • 可借高數中“輪換對稱性”的思想,在面對奇數金片時,把原來的 B 塔看成 C 塔,把原來的 C 塔看成 B 塔
  • 上面的操作有 2 個規律

    • 規律一
      • 因為每走一步,數值加 1,所以該二進制數即為步數
      • 該二進制數末尾 0 的個數對應要移動的金片編號
        • 沒有 0,即為 0 個 0,對應 0 號金片;可回顧圖 "0001"、"0011"、"0101"
        • 1 個 0,對應 1 號金片;可回顧圖 "0010"
        • 2 個 0,對應 2 號金片;可回顧圖 "0100"
        • 依此類推
    • 規律二
      • 編號為 0、2、4……的金片,總是進行右移操作
      • 編號為 1、3、5……的金片,總是進行左移操作
        • 三根柱子,右移 2 格即為左移 1 格

3) 計算移動次數

  • 按遞歸的思路,漢諾塔可分成三大步
    • 將 A 塔的上面 n-1 片金片移至 B 塔
    • 將 A 塔剩余的 1 片金片移至 C 塔
    • 將 B 塔的 n-1 片金片移至 C 塔
  • 設 f(n) 為 n 片金片完成移動需要的最少次數,則 f(n) = f(n-1) + 1 + f(n-1),即 f(n) = 2f(n-1) + 1
    • 若只有 1 片金片,則 f(1) = 1
    • 若有 2 片金片,則 f(2) = 3
    • 若有 3 片金片,則 f(3) = 7
    • 照此規律,可假設 f(n) = 2n - 1
  • 莫名想到線代中用的“第一類數學歸納法”,我獻丑證一下,算是溫故知新
    • 證明 f(n) = 2n - 1 成立:
    • 當 n = 1 時,f(1) = 21 - 1 = 1,成立
    • 當 n = k 時,設 f(k) = 2k - 1 成立
    • => 當 n = k + 1 時,f(k+1) = 2f(k) + 1 = 2 * (2k - 1) + 1 = 2k+1 - 1,滿足假設
    • => 漢諾塔的移動次數為 f(n) = 2n - 1,證畢

(3) 程序

1) 代碼

# 不使用遞歸

def hanoi(n):
    tower_belong = [0] * n  # 用列表開辟 n 個空間,用於存放 n 個金片各自的編號,編號對應塔號
                            # 金片移動,編號對應更改
    if n % 2 == 0:          # 金片數量不同,塔的排序不同
        tower_name = ['A', 'B', 'C']    # 若 n 為偶數,最終所有金片恰好能移到右塔
    else:
        tower_name = ['A', 'C', 'B']    # 若 n 為奇數,最終所有金片會移到中塔
                                        # 用“輪換對稱”將 B、C 兩塔互換名字,以實現“負負得正”
    for step in range(1, 2**n):         # n 片金片最少需要移動 2^n - 1 次
        bin_step = bin(step)            # 求得 step 的二進制數值
        gold_num = len(bin_step) - bin_step.rfind('1') - 1
        # 計算 step 末尾 0 的個數,得到金片編號;上面說的“規律一”
        # 如 step = 0b0001,則 step 末尾 0 的個數為 0,表示此刻應移動 0 號金片
        # 如 step = 0b0100,則 step 末尾 0 的個數為 2,表示此刻應移動 2 號金片,依此類推
        # rfind 是從 0 開始計數,所以再減個 1
        
        print(f"第 {step:2} 步:移動 {str(gold_num)} 號金片,從 {tower_name[tower_belong[gold_num]]} 塔到", end=' ')             # 移出金片的塔
        if gold_num % 2 == 0:                                   # 若 num 為 偶數,則右移
            tower_belong[gold_num] = (tower_belong[gold_num] + 1) % 3
            # 若從 C 塔右移,則又回到了 A 塔
        else:                                                   # 若 num 為奇數,則左移
            tower_belong[gold_num] = (tower_belong[gold_num] + 2) % 3
            # 若從 A 塔左移,則又去到了 C 塔
        print(tower_name[tower_belong[gold_num]], '塔')         # 移入金片的塔

# 清爽、無注釋版

def hanoi(n):
    tower_belong = [0] * n
    if n % 2 == 0:
        tower_name = ['A', 'B', 'C']
    else:
        tower_name = ['A', 'C', 'B']
    for step in range(1, 2**n):
        bin_step = bin(step)
        gold_num = len(bin_step) - bin_step.rfind('1') - 1
        
        print(f"第 {step:2} 步:移動 {str(gold_num)} 號金片,從 {tower_name[tower_belong[gold_num]]} 塔到", end=' ')
        if gold_num % 2 == 0:
            tower_belong[gold_num] = (tower_belong[gold_num] + 1) % 3
        else:
            tower_belong[gold_num] = (tower_belong[gold_num] + 2) % 3
        print(tower_name[tower_belong[gold_num]], '塔')

2) 運行情況

  • 3 層漢諾塔


  • 4 層漢諾塔



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM