文章发布于:2025年4月29日

更新:2025/5/5

一个星期以后,我想我弄明白了剪枝原理。做了一张图,介绍 max -> min 和 min -> max 两种不同顺序的剪枝逻辑。


想了解这个算法,用 deepseek 基于这个算法做了一个井字棋游戏,然后再基于实现代码,了解一下算法的具体工作过程。以下文章是豆包基于图片代码自动生成的。

这段代码是用 JavaScript 实现的,结合极小极大算法(Minimax Algorithm )与 Alpha - Beta 剪枝(Alpha - Beta Pruning),用于解决博弈类问题,比如井字棋(Tic - Tac - Toe)。以下是代码的详细解释:


整体思路


极小极大算法是一种用于博弈树搜索的递归算法,用于确定在二人零和博弈中,当前玩家的最优行动。Alpha - Beta 剪枝是对极小极大算法的优化,通过减少不必要的节点评估,提升算法效率。

代码逐段分析
1. 初始化最佳分数和最佳移动

let bestScore = -Infinity;
let bestMove = null;


这里定义了两个变量,bestScore用于记录当前找到的最佳分数,初始化为负无穷;bestMove用于记录最佳的移动位置,初始化为null 。
2. 外层循环寻找最佳移动

for (let i = 0; i < 9; i++) {
    if (board[i] ==='') {
        board[i] = aiPlayer;
        let score = minimax(board, 0, false, -Infinity, Infinity);
        board[i] ='';
        if (score > bestScore) {
            bestScore = score;
            bestMove = i;
        }
    }
}
makeMove(bestMove, aiPlayer);


这段代码遍历棋盘(假设棋盘是长度为 9 的数组,每个位置可以为空、代表 AI 玩家或人类玩家)。对于每个空位置,先让 AI 玩家占据该位置,然后调用minimax函数评估该局面的分数,再将位置恢复为空。如果当前分数高于之前记录的最佳分数,就更新最佳分数和最佳移动位置。最后根据找到的最佳移动,让 AI 玩家落子。
3. 核心的 minimax 函数

function minimax(board, depth, isMaximizing, alpha, beta) {
    const result = checkWinner(board);
    if (result!== null) {
        if (result.winner === aiPlayer) {
            return 10 - depth;
        } else if (result.winner === humanPlayer) {
            return depth - 10;
        } else {
            return 0;
        }
    }


函数接收当前棋盘状态board、搜索深度depth、标识当前是最大化玩家(AI )还是最小化玩家(人类)的isMaximizing ,以及 Alpha - Beta 剪枝用到的alpha和beta值。首先检查当前局面是否有赢家,如果有:
1. 如果 AI 是赢家,返回一个分数(10 减去当前深度,深度越小分数越高,鼓励 AI 尽早获胜 )。
2. 如果人类是赢家,返回深度减去 10(深度越大分数越低,惩罚 AI 失败 )。
3. 如果是平局,返回 0。

    if (isMaximizing) {
        let bestScore = -Infinity;
        for (let i = 0; i < 9; i++) {
            if (board[i] ==='') {
                board[i] = aiPlayer;
                let score = minimax(board, depth + 1, false, alpha, beta);
                board[i] ='';
                bestScore = Math.max(score, bestScore);
                alpha = Math.max(alpha, score);
                if (beta <= alpha) break;
            }
        }
        return bestScore;


如果当前是最大化玩家(AI ):
初始化最佳分数为负无穷。
遍历棋盘的空位置,让 AI 在该位置落子,递归调用minimax函数进入下一层搜索(深度加 1,同时切换为最小化玩家 ),再恢复棋盘状态。
更新最佳分数为当前分数和最佳分数中的较大值,同时更新alpha值(记录当前找到的最大分数 )。如果beta小于等于alpha,说明后续搜索不会改变当前结果,进行剪枝(跳出循环 )。最后返回最佳分数。

    } else {
        let bestScore = Infinity;
        for (let i = 0; i < 9; i++) {
            if (board[i] ==='') {
                board[i] = humanPlayer;
                let score = minimax(board, depth + 1, true, alpha, beta);
                board[i] ='';
                bestScore = Math.min(score, bestScore);
                beta = Math.min(beta, score);
                if (beta <= alpha) break;
            }
        }
        return bestScore;
    }
}


如果当前是最小化玩家(人类):
初始化最佳分数为正无穷。
遍历棋盘空位置,让人类在该位置落子,递归调用minimax函数(深度加 1,切换为最大化玩家 ),恢复棋盘状态。
更新最佳分数为当前分数和最佳分数中的较小值,同时更新beta值(记录当前找到的最小分数 )。如果beta小于等于alpha,进行剪枝,最后返回最佳分数。