Skip to content

作业4:极小极大搜索求解博弈问题 - 参考题解

1. 问题分析

本题是一个典型的双人零和博弈问题,具有以下特点:

  • 完全信息:双方都能看到所有牌的点数和位置
  • 轮流行动:小红和小蓝交替从牌堆两端取牌
  • 理性假设:双方都会采取最优策略
  • 目标明确:最大化自己的得分

这类问题的核心在于:如何在对手也采取最优策略的前提下,找到自己的最优决策

2. 两种求解思路

2.1 Minimax 搜索算法

核心思想:递归模拟博弈树,在每个节点上:

  • 当前玩家选择能让自己得分最大的移动
  • 假设对手在下一步会选择让对手自己得分最大(即让我得分最小)的移动

算法框架

minimax(当前状态, 是否轮到我):
    如果游戏结束:
        返回 (我的得分, 对手得分)
    
    遍历所有可能的移动:
        执行移动,得到新状态和本次得分
        递归调用 minimax(新状态, 轮到对手)
        计算总得分 = 本次得分 + 未来我的得分
    
    返回让我得分最大的移动对应的结果

优点

  • 逻辑直观,符合博弈思维
  • 容易理解和实现
  • 适合搜索树较小的问题

2.2 动态规划 (DP) 方法

核心思想:将问题转化为状态价值函数的计算:

  • 定义 DP[状态] = 在该状态下,轮到行动的玩家相对对手的得分优势
  • 通过自底向上或记忆化的方式填充 DP 表

算法框架

dp(当前状态):
    如果状态已计算过:
        返回缓存结果
    
    如果游戏结束:
        返回 0(得分优势为0)
    
    遍历所有可能的移动:
        执行移动,得到新状态和本次得分
        递归计算 dp(新状态)
        当前优势 = 本次得分 - dp(新状态)
    
    返回最大优势,并缓存结果

优点

  • 通过"得分差"统一了双方视角,代码更简洁
  • 天然支持记忆化,避免重复计算
  • 适合状态空间较大但有重叠子问题的场景

2.3 两种方法的联系

实际上,Minimax 搜索 + 记忆化 ≈ 动态规划。两者本质相同,只是:

  • Minimax 更强调"搜索"和"博弈树"的概念
  • DP 更强调"状态转移"和"最优子结构"

在本题中,由于状态空间有限且存在大量重叠子问题,两种方法都需要记忆化才能在时限内通过。

3. 性能优化策略

3.1 记忆化搜索(必需)

问题:朴素的递归会重复计算相同的状态。例如:

  • 小红从第1行左端拿牌 → 小蓝从第2行右端拿牌
  • 小红从第2行右端拿牌 → 小蓝从第1行左端拿牌

这两条路径会到达相同的状态,但朴素递归会重复计算。

解决方案:使用哈希表(字典)缓存已计算的状态:

  • 状态表示:用元组或整数编码当前每行的左右边界
  • 缓存策略:计算完一个状态后立即存入字典
  • 查询优先:每次递归前先查字典,命中则直接返回

效果:将指数级时间复杂度降低到多项式级,是通过测试的关键。

3.2 Alpha-Beta 剪枝(可选)

原理:在搜索过程中维护两个值:

  • Alpha:当前玩家已知的最低得分保证
  • Beta:对手已知的最高得分保证

当发现某个分支无论如何都不会被选择时,提前终止搜索。

效果:进一步减少搜索节点数,但在本题中由于记忆化已经很高效,剪枝的提升有限。

3.3 状态表示优化

挑战:Python 的元组和字典操作相对较慢,在高频调用时会成为瓶颈。

优化方向

  • 使用整数编码状态(位运算)
  • 使用数组代替元组
  • 减少状态拷贝次数

4. 高性能实现方案(不必需)

由于 Python 的解释执行特性,在处理密集计算时性能有限。对于本题的大规模测试数据(n=50),采用加速技术能得到性能更好的解决方案:

4.1 方案 A:Numba JIT 编译

技术原理

  • Numba 是一个 Python 的即时编译器,可以将 Python 函数编译为机器码
  • 通过 @njit 装饰器标记需要加速的函数
  • 使用 Numpy 数组和 Numba 类型化字典以获得最佳性能

优势

  • 开发成本低,代码仍然是 Python 风格
  • 性能接近 C 语言(通常有 10-100 倍加速)
  • 无需编写 C 代码,易于调试和维护

适用场景

  • 核心算法逻辑清晰,适合用 Python 表达
  • 需要快速原型开发和迭代
  • 对性能有较高要求但不追求极致

4.2 方案 B:C 语言扩展

技术原理

  • 将核心的 DP 或 递归函数用 C 语言实现
  • 在运行时通过 gcc 编译为动态链接库(.so 文件)
  • 使用 ctypes 在 Python 中调用 C 函数

优势

  • 性能极致,充分利用 C 语言的底层优化
  • 对内存和指令级别有完全控制
  • 适合处理超大规模数据

劣势

  • 开发复杂度高,需要处理类型转换和内存管理
  • 调试困难,C 代码错误可能导致段错误
  • 跨平台兼容性需要额外考虑

适用场景

  • 对性能有极致要求
  • 算法逻辑稳定,不需要频繁修改
  • 开发者熟悉 C 语言和 Python/C 接口

4.3 方案对比

维度Numba JITC 语言扩展
开发难度⭐⭐⭐⭐⭐⭐
性能⭐⭐⭐⭐⭐⭐⭐⭐⭐
可维护性⭐⭐⭐⭐⭐⭐
调试友好度⭐⭐⭐⭐⭐⭐
适用规模中大型超大型

建议

  • 如果你采用的是动态规划算法,那么不用高性能优化也能通过本题。
  • 对于 minimax 搜索+记忆话,可能需要 Numba JIT 优化方案。
  • 如果追求极致的性能优化,可以尝试 C/Python 混合编程。

Released under the MIT License.