rokevin
移动
前端
语言
  • 基础

    • Linux
    • 实施
    • 版本构建
  • 应用

    • WEB服务器
    • 数据库
  • 资讯

    • 工具
    • 部署
开放平台
产品设计
  • 人工智能
  • 云计算
计算机
其它
GitHub
移动
前端
语言
  • 基础

    • Linux
    • 实施
    • 版本构建
  • 应用

    • WEB服务器
    • 数据库
  • 资讯

    • 工具
    • 部署
开放平台
产品设计
  • 人工智能
  • 云计算
计算机
其它
GitHub
  • 汉诺塔

  • 算法定义
  • 算法思路
  • 算法复杂度
  • 优点与缺点
  • 应用场景
  • Java 实现及优化版本
  • 各版本优化说明
  • 资料

汉诺塔

算法定义

汉诺塔是一个经典的递归问题,源于印度古老传说。问题描述为:有 3 根柱子(A、B、C)和 n 个大小不同的圆盘,所有圆盘初始套在 A 柱上,且大盘按从小到大顺序叠放(大圆盘在下,小圆盘在上)。要求将所有圆盘从 A 柱移动到 C 柱,过程中需遵守:

  • 每次只能移动一个圆盘
  • 任何时候都不能将大圆盘放在小圆盘上
  • B 柱作为辅助柱使用

汉诺塔算法是解决该问题的步骤性策略,核心通过递归思想将复杂问题分解为简单子问题。

算法思路

汉诺塔算法的递归思路可归纳为:

  1. 基线条件:当只有 1 个圆盘时,直接将其从源柱移动到目标柱。
  2. 递归分解:
    • 将 n-1 个圆盘从源柱(A)通过目标柱(C)移动到辅助柱(B)
    • 将第 n 个圆盘(最大的圆盘)从源柱(A)直接移动到目标柱(C)
    • 将 n-1 个圆盘从辅助柱(B)通过源柱(A)移动到目标柱(C)

例如,n=3 时的移动步骤:

  • 步骤 1:将 2 个圆盘从 A→B(以 C 为辅助)
  • 步骤 2:将第 3 个圆盘从 A→C
  • 步骤 3:将 2 个圆盘从 B→C(以 A 为辅助)

算法复杂度

  • 时间复杂度:O (2ⁿ),因为每增加 1 个圆盘,移动次数约翻倍(总次数为 2ⁿ - 1)
  • 最坏时间复杂度:O (2ⁿ),问题规模固定时,移动步骤唯一
  • 平均时间复杂度:O (2ⁿ),不存在输入数据分布差异
  • 空间复杂度:O (n),递归调用栈深度为 n(等于圆盘数量)

优点与缺点

  • 优点:
    • 逻辑清晰,递归实现代码简洁优雅
    • 问题分解方式具有启发性,适合理解递归思想
    • 解法具有唯一性,能找到最优移动路径(最少步数)
  • 缺点:
    • 时间复杂度高(指数级),当 n 较大时(如 n>20)实际不可用
    • 递归深度受限于栈内存,n 过大会导致栈溢出
    • 非递归实现复杂,不易理解

应用场景

  • 递归算法教学(展示递归思想的经典案例)
  • 程序设计中的递归逻辑验证
  • 心理学问题解决研究(问题分解能力测试)
  • 工业领域的堆叠问题(如磁盘调度、货物装卸的步骤规划)
  • 数学中的组合问题(如置换群、分形结构)

Java 实现及优化版本

public class HanoiTower {
    // 移动次数计数器
    private static int moveCount = 0;
    
    // 1. 基本递归版(打印移动步骤)
    public static void hanoiBasic(int n, char source, char auxiliary, char target) {
        // 基线条件:只有一个圆盘时直接移动
        if (n == 1) {
            moveCount++;
            System.out.printf("步骤%d: 从%c移动到%c\n", moveCount, source, target);
            return;
        }
        
        // 递归步骤1:将n-1个圆盘从源柱移到辅助柱
        hanoiBasic(n - 1, source, target, auxiliary);
        
        // 步骤2:将第n个圆盘从源柱移到目标柱
        moveCount++;
        System.out.printf("步骤%d: 从%c移动到%c\n", moveCount, source, target);
        
        // 递归步骤3:将n-1个圆盘从辅助柱移到目标柱
        hanoiBasic(n - 1, auxiliary, source, target);
    }
    
    // 2. 优化版1:计数不打印(高效计算移动次数)
    public static long hanoiCountOnly(int n) {
        // 数学公式:n个圆盘需要2^n - 1次移动
        return (1L << n) - 1; // 等价于2^n - 1(仅适合n<=63)
    }
    
    // 3. 优化版2:非递归实现(用栈模拟递归,避免栈溢出)
    public static void hanoiNonRecursive(int n, char source, char auxiliary, char target) {
        java.util.Stack<int[]> stack = new java.util.Stack<>();
        // 入栈参数:n, source, auxiliary, target, 是否处理过(0=未处理,1=已处理)
        stack.push(new int[]{n, source, auxiliary, target, 0});
        moveCount = 0;
        
        while (!stack.isEmpty()) {
            int[] current = stack.pop();
            int num = current[0];
            char s = (char) current[1];
            char a = (char) current[2];
            char t = (char) current[3];
            int processed = current[4];
            
            if (num == 1) {
                // 单个圆盘直接移动
                moveCount++;
                System.out.printf("步骤%d: 从%c移动到%c\n", moveCount, s, t);
            } else if (processed == 0) {
                // 第一次处理,按逆序入栈(栈是LIFO)
                stack.push(new int[]{num, s, a, t, 1});          // 标记为已处理
                stack.push(new int[]{num - 1, a, s, t, 0});      // 步骤3
                stack.push(new int[]{num, s, a, t, 2});          // 步骤2(实际移动)
                stack.push(new int[]{num - 1, s, t, a, 0});      // 步骤1
            } else if (processed == 2) {
                // 执行实际移动操作
                moveCount++;
                System.out.printf("步骤%d: 从%c移动到%c\n", moveCount, s, t);
            }
        }
    }
    
    // 4. 优化版3:大数量圆盘的移动次数计算(支持n>63)
    public static String hanoiBigCount(int n) {
        // 使用字符串计算2^n - 1,避免长整数溢出
        StringBuilder result = new StringBuilder("1");
        // 计算2^n
        for (int i = 0; i < n; i++) {
            result = multiplyByTwo(result);
        }
        // 减1
        return subtractOne(result).toString();
    }
    
    // 辅助方法:字符串表示的数字乘以2
    private static StringBuilder multiplyByTwo(StringBuilder num) {
        int carry = 0;
        for (int i = num.length() - 1; i >= 0; i--) {
            int digit = num.charAt(i) - '0';
            int product = digit * 2 + carry;
            num.setCharAt(i, (char) (product % 10 + '0'));
            carry = product / 10;
        }
        if (carry > 0) {
            num.insert(0, carry);
        }
        return num;
    }
    
    // 辅助方法:字符串表示的数字减1
    private static StringBuilder subtractOne(StringBuilder num) {
        int i = num.length() - 1;
        while (i >= 0 && num.charAt(i) == '0') {
            num.setCharAt(i, '9');
            i--;
        }
        if (i < 0) {
            return new StringBuilder("0");
        }
        num.setCharAt(i, (char) (num.charAt(i) - 1));
        // 移除前导零
        if (num.charAt(0) == '0' && num.length() > 1) {
            return new StringBuilder(num.substring(1));
        }
        return num;
    }
    
    public static void main(String[] args) {
        int n = 3;
        System.out.println("=== 基本递归版(n=" + n + ")===");
        moveCount = 0;
        hanoiBasic(n, 'A', 'B', 'C');
        
        System.out.println("\n=== 非递归版(n=" + n + ")===");
        hanoiNonRecursive(n, 'A', 'B', 'C');
        
        System.out.println("\n=== 移动次数计算 ===");
        System.out.println("n=10的移动次数: " + hanoiCountOnly(10));
        System.out.println("n=100的移动次数: " + hanoiBigCount(100));
    }
}

各版本优化说明

  1. 基本递归版: 最经典的实现方式,直接体现递归分解思想,代码简洁且能打印具体移动步骤,但 n 较大时(如 n>20)会因递归深度过大导致栈溢出,且打印步骤会消耗大量时间。
  2. 计数不打印版: 利用数学公式(移动次数 = 2ⁿ-1)直接计算结果,时间复杂度 O (1),适合仅需知道总步数而无需具体移动路径的场景,缺点是 n>63 时会超出 long 类型范围。
  3. 非递归实现: 使用栈模拟递归调用过程,避免了递归栈溢出问题,可处理更大的 n 值(受栈内存限制),但代码复杂度增加,可读性降低。
  4. 大数量计数版: 通过字符串操作实现超大数(n>63)的移动次数计算,解决了数值溢出问题,适合理论研究中对超大 n 值的步数估算。

汉诺塔算法是递归思想的完美诠释,其核心价值在于展示了 "将复杂问题分解为相似子问题" 的解题思路。实际应用中,基本递归版适合教学和小规模问题,非递归版适合需要避免栈溢出的场景,而公式计算版则适合快速获取移动次数。由于时间复杂度为指数级,汉诺塔算法更多作为理论研究和教学案例,而非实际工程中的高效算法。

资料

汉诺塔问题及其变种

https://www.cnblogs.com/jingmoxukong/p/4359185.html

https://www.zhihu.com/question/24385418

最近更新:: 2025/9/27 00:43
Contributors: luokaiwen