從“漢諾塔”看遞迴演算法
遞迴演算法是《資料結構與演算法》中最簡潔的演算法之一,它可以非常簡明地描述“減而治之”(decrease and conquer)和“分而治之”(divide and conquer)這兩種演算法思想。遞迴演算法雖然從程式碼角度來看非常簡單,但對於新手理解起來卻不那麼簡單。本文我將結合《資料結構與演算法》的專業描述和《程式設計師的數學》的通俗描述,並以“漢諾塔”為例來講解我對“遞迴”演算法的理解,並給出 Python 程式碼描述。
一、漢諾塔迷題
“漢諾塔”是由數學家 Edouard Lucas 於1883 年發明的遊戲,具體內容可以看 wiki - 漢諾塔 ,下面給出 3 層漢諾塔的移動示意圖:
從上圖可以觀察到,過程 1、2、3 和過程 5、6、7是非常相似的,前者將 2 個圓盤從 A 移到 C ;後者將 2 個圓盤從 C 移到 B 。總結如下:
二、遞迴思維
從上面對“漢諾塔”迷題的觀察和分析可以清楚的反映“遞迴”的思維方式:當我們碰到“簡單問題易解(一層漢諾塔),複雜問題難解(多層漢諾塔)”時,嘗試問自己“能將複雜的問題轉化為較為簡單的同類問題嗎?”
這就是遞迴的思維方式,對於漢諾塔來說,就是將 n 層漢諾塔轉換為 n-1 層漢諾塔問題,即在問題中找出遞迴結構。
如果找到了遞迴結構,接下來就是根據遞迴結構建立遞迴公式
這就是 n 層漢諾塔移動的步數。
換成專業術語來描述:遞迴演算法的關鍵就是找到遞迴基(base case)和遞推公式(recursion relation, recurrence)。其中,遞迴基一般比較容易找到,而遞推公式往往隱藏在大量的細節中,需要我們去分析提取。一般來說,分析提出遞迴公式,先要歸納出遞迴例項
三、程式碼實現
下面通過程式碼來體會漢諾塔問題的遞迴演算法
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
r'''
tower_of_hanoi.py
Usage:
python3 tower_of_hanoi.py
'''
def move(n, source, target, auxiliary):
# base case
if n == 1:
print(source, '-->', target)
return
# first step: move n - 1 disks from source to auxiliary, so they are out of the way
move(n-1, source, auxiliary, target)
# second step: move the nth disk from source to target
move(1, source, target, auxiliary)
# last step: move the n - 1 disks that we left on auxiliary onto target
return move(n-1, auxiliary, target, source)
if __name__ == '__main__':
# initiate call from source A to target C with auxiliary B
move(3, 'A', 'C', 'B')
從上面的程式碼可以看出:遞迴演算法的確很簡潔,真個演算法,除了 base-case 外,就剩三次遞迴呼叫,這三次遞迴呼叫也清晰的描述了該問題的遞迴結構。