汉诺塔(Tower of Hanoi)

📌 定义

汉诺塔是一个经典的递归问题:有三根柱子A、B、C,A柱上有n个盘子(从小到大叠放),要求将所有盘子从A柱移动到C柱,移动规则:

  1. 每次只能移动一个盘子
  2. 大盘子不能放在小盘子上面
  3. 可以使用B柱作为辅助
初始状态:          目标状态:
A    B    C       A    B    C
|    |    |       |    |    |
-    |    |       |    |    -
--   |    |       |    |   --
---  |    |       |    |  ---

核心思路

使用递归分治思想:

  1. 将n-1个盘子从A移到B(使用C作为辅助)
  2. 将最大的盘子从A移到C
  3. 将n-1个盘子从B移到C(使用A作为辅助)
n=3的情况:

步骤1: 将2个盘子从A移到B
  A    B    C
  |    -    |
  |   --    |
  ---  |    |

步骤2: 将最大盘子从A移到C
  A    B    C
  |    -    |
  |   --    |
  |    |   ---

步骤3: 将2个盘子从B移到C
  A    B    C
  |    |    -
  |    |   --
  |    |   ---

复杂度分析

指标复杂度说明
时间复杂度O(2^n)移动次数 = 2^n - 1
空间复杂度O(n)递归调用栈深度
最少步数2^n - 1已被证明是最优解

Go 代码

Go 实现

package main
 
import (
    "fmt"
    "math"
)
 
func hanoi(n int, source, target, auxiliary rune) {
    if n == 1 {
        fmt.Printf("移动盘子 1 从 %c 到 %c\n", source, target)
        return
    }
 
    hanoi(n-1, source, auxiliary, target)
    fmt.Printf("移动盘子 %d 从 %c 到 %c\n", n, source, target)
    hanoi(n-1, auxiliary, target, source)
}
 
type Move struct {
    From rune
    To   rune
}
 
func hanoiMoves(n int) []Move {
    var moves []Move
 
    var move func(int, rune, rune, rune)
    move = func(n int, source, target, auxiliary rune) {
        if n == 1 {
            moves = append(moves, Move{source, target})
            return
        }
 
        move(n-1, source, auxiliary, target)
        moves = append(moves, Move{source, target})
        move(n-1, auxiliary, target, source)
    }
 
    move(n, 'A', 'C', 'B')
    return moves
}
 
func main() {
    n := 3
    fmt.Printf("汉诺塔: %d个盘子\n", n)
    hanoi(n, 'A', 'C', 'B')
 
    totalMoves := int(math.Pow(2, float64(n))) - 1
    fmt.Printf("\n总共需要 %d 步\n", totalMoves)
}

思路展开

递归树(n=3)

                    hanoi(3, A, C, B)
                    /       |       \
        hanoi(2,A,B,C)   移动3:A->C   hanoi(2,B,C,A)
         /      |      \              /      |      \
hanoi(1,A,C,B) 移2:A->B hanoi(1,C,B,A) hanoi(1,B,A,C) 移2:B->C hanoi(1,A,C,B)
      |                      |              |                      |
   移1:A->C              移1:C->B        移1:B->A              移1:A->C

移动序列:
1. A -> C
2. A -> B
3. C -> B
4. A -> C
5. B -> A
6. B -> C
7. A -> C

移动次数推导

T(n) = 移动n个盘子需要的步数

T(1) = 1
T(n) = T(n-1) + 1 + T(n-1)
     = 2*T(n-1) + 1

展开:
T(n) = 2*T(n-1) + 1
     = 2*(2*T(n-2) + 1) + 1
     = 2²*T(n-2) + 2 + 1
     = 2³*T(n-3) + 2² + 2 + 1
     = ...
     = 2^(n-1)*T(1) + 2^(n-2) + ... + 2 + 1
     = 2^(n-1) + (2^(n-1) - 1)
     = 2^n - 1

经典题目

基础应用

变体问题

  • 四柱汉诺塔(Frame-Stewart算法)
  • 限制移动方向的汉诺塔
  • 汉诺塔II:只能相邻柱子移动

💡 变体问题

⚖️ 优缺点

优点

  • ✅ 递归典范:完美展示递归思想
  • ✅ 数学之美:移动次数2
  • ✅ 教学价值:帮助理解分治和递归

缺点

  • ❌ 指数复杂度:O(2
  • ❌ 实际应用少:主要用于教学

🎨 应用场景

  1. 教学工具:递归和分治思想的经典例子
  2. 备份系统:分层数据迁移
  3. 游戏设计:益智游戏的基础
  4. 算法思维:培养递归思维

🔍 数学性质

1. 最少移动次数

移动n个盘子需要的最少步数:

T(n) = 2^n - 1

证明:

  • T(1) = 1
  • T(n) = 2*T(n-1) + 1
  • 递推得 T(n) = 2^n - 1

2. 格雷码关系

汉诺塔的移动序列与格雷码(Gray Code)一一对应:

  • 每一步移动对应格雷码的一位变化
  • 第i步移动的盘子编号 = 第i个格雷码变化的位

3. 传说故事

传说中有64个金盘的汉诺塔,僧侣每天移动一个盘子:

2^64 - 1 ≈ 1.844 × 10^19 步
按每秒一步计算:约5850亿年

相关主题


返回:分治算法 | 算法学习导航