数据结构与算法分析:专题内容——人工智能中的寻路7之AlphaBeta(代码详解)

一、算法描述

在考虑到对手的可能走法之后,Minimax算法能够较为恰当地找出玩家的最优走法。但是,在生成博弈树时,这个信息却没有使用!我们看看早先介绍的BoardEvaluation评分函数。回忆一下下图Minimax的探测:
在这里插入图片描述
这是从初始状态开始,玩家X走了两步,玩家O走了一步之后,生成的博弈树的一部分。我们可以注意到,Minimax算法的性能非常低下,即使每一个子搜索都能够表示一个负棋面状态。总共有36个节点需要评价。
Minimax并不会利用玩家O第一步走在左上角避免被秒杀这个事实。AlphaBeta算法使用一个固定的策略来从搜索树中剪除无效的搜索,而不是在搜索中采取特殊的策略来使用信息。使用AlphaBeta算法的博弈树等价扩展如下图所示。
在这里插入图片描述

二、复杂度分析

在这里插入图片描述

虽然得到的走法和Minimax的走法很类似,但是主要的改进就是大量节省了时间,大量的状态被从博弈树中剪除掉了。
因为AlphaBeta返回的结果和Minimax以及NegMax计算出来的几乎相同,那么唯一能够看出AlphaBeta好处的地方就是检查生成的博弈树的规模。这个任务是非常复杂的,因为如果对手的最优走法首先被评价,AlphaBeta能够节省大量的时间。当每个局面状态有b个可行走法时,追寻深度为d的AlphaBeta算法可能搜索的状态数目大约为 b d b^d bd。如果走法是按照某种顺序降序排列,那么我们仍然需要评价所有的b个子节点(因为我们要选择最优的走法),但是,最好情况下我们只需要评价对手的第一种走法。在AlphaBeta追寻深度为2的图中,由于走法有序,在评价了一些走法之后,就能够进行剪枝,所以在这棵博弈树下,走法排序并不是最优的。
最好情况下,AlphaBeta在每一级为玩家评价b个局面状态,但是只评价对手的一个局面状态。所以,在博弈树的第d级,AlphaBeta只需要评价b1b*……b1个局面状态,而不是扩展出bbb*……bb个局面状态。所以局面状态的总数是bd/2,节省了巨大的开销。
AlphaBeta并不会试图简单地最小化状态的数目,AlphaBeta扩展的数目有可能和Minimax一样多,但是这种情况下,AlphaBeta扩展的深度是Minimax的两倍。
从经验上来比较Minimax和AlphaBeta,我们构建了一个一字棋初始棋面状态的集合,这些棋面状态至少能够走k步。然后定义追寻深度为d=9-k,保证所有可能的走法都会探测到,然后对比Minimax和AlphaBeta。结果见下表。我们看到AlphaBeta算法剪除掉了大量的状态。

kMinimax状态数AlphaBeta状态数减少程度波动范围
1549,94527,56595%±1.3%
2549,93647,50891%±6.8%
3549,864112,08680%±10.2%

每个比较都说明了AlphaBeta的巨大改进。而且有些情况能够解释为什么AlphaBeta如此优秀,例如这个局面状态:
在这里插入图片描述
AlphaBeta只需扩展47的局面状态(Minimax需要扩展8232个,节省了99.4%)来告知玩家X应该选择中间的方块。但是,能够得到这样大的性能改进的前提是可行走法有序,并且最优走法排在最前面。因为我们的一字棋的解不会排序走法,可能会得到一些异常结果。例如,将上面的棋面旋转180°:
在这里插入图片描述
那么AlphaBeta只需要探测960个局面状态(节省了88.3%)。

三、适用情况

AlphaBeta算法也是搜索最好的走法,它能够记住如果玩家O走在左上角,那么玩家X的得分不会超过2这样的信息。对于玩家O的后续每一步,AlphaBeta会决定是否玩家X有至少一步这样的走法,这步走法能够比玩家O第一步要好。因此,博弈树只扩展了16个节点(相比Minimax节省了50%的开销)。更重要的是,如果这个部分的结果无关紧要,那么算法就不需要预计算可能的走法并且修改状态。
AlphaBeta选择Minimax也同样会选择的走法,但是会节省大量的开销。和之前描述的其他寻路算法一样,AlphaBeta算法也假设对手不会犯错,这样避免了搜索那些对局面没有影响的部分。AlphaBeta递归地搜索博弈树,并且维护两个值,α和β,如果α<β,那么表示玩家α有胜利的机会。值α表示已经为玩家找到的最坏局面状态(如果没有找到即负无穷),并且声明玩家的走法能够保证不低于这个下界。α越大表示玩家能够表现得越好,如果α等于正无穷,那么玩家能够找到一步绝杀,并且搜索中值。β值表示现在的最好局面状态(如果没有找到即正无穷),即我们玩家能够达到的最好局面状态。β越小表示对手在限制玩家方面表现得越好。因为AlphaBeta算法有一个最大追寻深度,所以任何决定都受这个深度限制。
上图的博弈树给出了执行时候的[α,β]值;最开始为[-∞,∞]。使用追寻深度为2的搜索,仅仅只考虑玩家X接下来会采取的一步走法,AlphaBeta试着为玩家O找出最优走法。因为AlphaBeta是递归的,我们能够追寻其进程,对博弈树进行遍历。AlphaBeta指导玩家O首先应该占住左上角的位置。在评价了玩家X的5个可行走法之后,很明显玩家X只能保证α最小等于-2(使用一字棋的BoardEvaluation静态评估函数)。当AlphaBeta考虑O的第二步时,其[α,β]值现在是[-2,∞],即“O做出最坏的决定能够达到的状态得分为-2,最好的决定可能赢得比赛”。当评价完玩家X的第一步对策,AlphaBeta发现X已经赢得比赛,所以将不会再考虑X的更多走法。
回忆一下AlphaBeta搜索是基于NegMax。AlphaBeta保证不会搜索无用节点。下图是在图Minimax的基础上进行追寻深度为3的搜索,我们可以看到AlphaBeta是如何剪枝的,Minimax的博弈树需要156个节点,而AlphaBeta博弈树只扩展了66个节点。
在这里插入图片描述
初始位置为节点n,玩家O需要考虑6个可行走法。在轮到玩家或者对手时都可以进行键值。在上图的搜索中,有两个这样的例子。

  • 轮到玩家
    假设玩家O在下在左列中间的位置,X于是下在顶行中间的位置(搜索树中这是根节点的最左边的孙子节点)。现在,从O的角度来看,O能够得到的最好的分数是-1。当X走在底行中间,我们决定是否O能够赢得比赛时,将会用到这个值。这个时候[α,β]是[-∞,-1]。当O走在顶行的中间时,AlphaBeta进行评价,得到分数为1,因为这个值大于-1,所以在这个层级上的O剩余三个走法都可以忽略。
  • 轮到对手
    假设玩家O在下在左列中间的位置,X于是下在右上角,那么将会立刻赢得比赛。AlphaBeta将不会考虑X的其他两个可行走法,因为在以玩家O上一步走法为根节点的子树中,其剩余的搜索节点都会剪除。

搜索将会剪掉那些确定会失败的子树。当基于Minimax扩展出AlphaBeta时,存在两种剪枝的方法,α剪枝和β剪枝,当基于NegMax扩展出简单的AlphaBeta时,那么只有一种剪枝的方法。因为AlphaBeta是递归的,那么[α,β]表示玩家的获胜机会大小,[-β,α]表示对手获胜机会的大小。在递归调用AlphaBeta时,会轮流从玩家和对手的角度来考虑问题。AlphaBeta会返回Minimax(如果是基于NegMax扩展,返回和NegMax相同的结果)相同的结果,但是它只会很少地扩展博弈树。AlphaBeta仍然使用深度的限制,这样来看,它的行为非常类似于深度优先搜索。

四、算法实现

输入输出和Minimax相同。主要的区别是AlphaBeta将会利用已经计算过的状态来决定是否继续搜索特定的子树。
AlphaBeta实现基于NegMax扩展。一旦当前局面状态下,玩家不能保证一个更好的位置(α剪枝)或者对手不能强迫玩家走到一个更坏的位置(β剪枝),那么搜索终止。

#include <iostream>
#include <vector>
#include <memory>
#include <limits>
#include <algorithm>// Forward declarations
class IGameState;
class IGameMove;
class IPlayer;
class IEvaluation;// Interface for game state
class IGameState {
public:virtual std::shared_ptr<IGameState> copy() = 0;virtual ~IGameState() = default;
};// Interface for game move
class IGameMove {
public:virtual void execute(std::shared_ptr<IGameState> state) = 0;virtual void undo(std::shared_ptr<IGameState> state) = 0;virtual ~IGameMove() = default;
};// Interface for player
class IPlayer {
public:virtual std::vector<std::shared_ptr<IGameMove>> validMoves(std::shared_ptr<IGameState> state) = 0;virtual int eval(std::shared_ptr<IGameState> state) = 0;virtual ~IPlayer() = default;
};// Evaluation interface
class IEvaluation {
public:virtual std::shared_ptr<IGameMove> bestMove(std::shared_ptr<IGameState> state,std::shared_ptr<IPlayer> player,std::shared_ptr<IPlayer> opponent) = 0;virtual ~IEvaluation() = default;
};// Move evaluation class
class MoveEvaluation {
public:std::shared_ptr<IGameMove> move;int score;explicit MoveEvaluation(int score) : move(nullptr), score(score) {}MoveEvaluation(std::shared_ptr<IGameMove> m, int s) : move(m), score(s) {}static int minimum() { return std::numeric_limits<int>::min(); }static int maximum() { return std::numeric_limits<int>::max(); }
};// AlphaBeta evaluation class
class AlphaBetaEvaluation : public IEvaluation {
private:std::shared_ptr<IGameState> state;int ply;MoveEvaluation alphabeta(int ply, std::shared_ptr<IPlayer> player,std::shared_ptr<IPlayer> opponent,int alpha, int beta) {// Get valid movesauto moves = player->validMoves(state);// If leaf node or no moves available, return evaluationif (ply == 0 || moves.empty()) {return MoveEvaluation(player->eval(state));}// Initialize best move with alphaMoveEvaluation best(alpha);// Try each move and update alphafor (const auto& move : moves) {// Execute movemove->execute(state);// Recursively evaluate positionMoveEvaluation me = alphabeta(ply - 1, opponent, player, -beta, -alpha);// Undo movemove->undo(state);// Update alpha if better move foundif (-me.score > alpha) {alpha = -me.score;best = MoveEvaluation(move, alpha);}// Pruning conditionif (alpha >= beta) {return best;}}return best;}public:explicit AlphaBetaEvaluation(int searchPly) : ply(searchPly) {}std::shared_ptr<IGameMove> bestMove(std::shared_ptr<IGameState> s,std::shared_ptr<IPlayer> player,std::shared_ptr<IPlayer> opponent) override {state = s->copy();MoveEvaluation move = alphabeta(ply, player, opponent,MoveEvaluation::minimum(),MoveEvaluation::maximum());return move.move;}
};// Example implementations for testing
class TicTacToeState : public IGameState {
public:std::vector<int> board;TicTacToeState() : board(9, 0) {}std::shared_ptr<IGameState> copy() override {auto newState = std::make_shared<TicTacToeState>();newState->board = this->board;return newState;}
};class TicTacToeMove : public IGameMove {
public:int position;int player; // 1 or -1TicTacToeMove(int pos, int p) : position(pos), player(p) {}void execute(std::shared_ptr<IGameState> state) override {auto gameState = std::dynamic_pointer_cast<TicTacToeState>(state);gameState->board[position] = player;}void undo(std::shared_ptr<IGameState> state) override {auto gameState = std::dynamic_pointer_cast<TicTacToeState>(state);gameState->board[position] = 0;}
};class TicTacToePlayer : public IPlayer {
private:int playerMark;public:explicit TicTacToePlayer(int mark) : playerMark(mark) {}std::vector<std::shared_ptr<IGameMove>> validMoves(std::shared_ptr<IGameState> state) override {auto gameState = std::dynamic_pointer_cast<TicTacToeState>(state);std::vector<std::shared_ptr<IGameMove>> moves;for (int i = 0; i < 9; i++) {if (gameState->board[i] == 0) {moves.push_back(std::make_shared<TicTacToeMove>(i, playerMark));}}return moves;}int eval(std::shared_ptr<IGameState> state) override {auto gameState = std::dynamic_pointer_cast<TicTacToeState>(state);// Check for win conditionsconst std::vector<std::vector<int>> lines = {{0,1,2}, {3,4,5}, {6,7,8}, // rows{0,3,6}, {1,4,7}, {2,5,8}, // columns{0,4,8}, {2,4,6}           // diagonals};for (const auto& line : lines) {int sum = 0;for (int pos : line) {sum += gameState->board[pos];}if (sum == 3 * playerMark) return 1000;if (sum == -3 * playerMark) return -1000;}return 0;}
};int main() {// Create initial game stateauto gameState = std::make_shared<TicTacToeState>();// Create playersauto player1 = std::make_shared<TicTacToePlayer>(1);  // X playerauto player2 = std::make_shared<TicTacToePlayer>(-1); // O player// Create evaluator with search depth of 4AlphaBetaEvaluation evaluator(4);// Make some moves to set up a game stategameState->board = {1, 0, -1,0, 1, 0,-1, 0, 0};// Get best move for player1auto bestMove = evaluator.bestMove(gameState, player1, player2);if (bestMove) {auto move = std::dynamic_pointer_cast<TicTacToeMove>(bestMove);std::cout << "Best move for Player 1 (X): position " << move->position << std::endl;// Execute the move to show the resulting boardmove->execute(gameState);std::cout << "\nResulting board:\n";for (int i = 0; i < 9; i++) {char symbol = gameState->board[i] == 1 ? 'X' : gameState->board[i] == -1 ? 'O' : '.';std::cout << symbol;if (i % 3 == 2) std::cout << "\n";}} else {std::cout << "No valid moves available\n";}return 0;
}

五、引用及参考文献

1.《算法设计手册》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/8114.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

12、本地缓存分布式缓存(未完待续)

1、哪些数据适合放入缓存&#xff1f; 即时性、数据一致性要求不高的访问量大且更新频率不高的数据&#xff08;读多&#xff0c;写少&#xff09; 2、本地缓存 1、本地缓存&#xff0c;如果是单体项目&#xff0c;部署到一台服务器上&#xff0c;就不存在什么问题&#xff…

Linux——网络基础(1)

文章目录 目录 文章目录 前言 一、文件传输协议 应用层 传输层 网络层 数据链路层 数据接收与解封装 主机与网卡 数据传输过程示意 二、IP和MAC地址 定义与性质 地址格式 分配方式 作用范围 可见性与可获取性 生活例子 定义 用途 特点 联系 四、TCP和UDP协…

免费GPU算力,不花钱部署DeepSeek-R1

在人工智能和大模型技术飞速发展的今天&#xff0c;越来越多的开发者和研究者希望能够亲自体验和微调大模型&#xff0c;以便更好地理解和应用这些先进的技术。然而&#xff0c;高昂的GPU算力成本往往成为了阻碍大家探索的瓶颈。幸运的是&#xff0c;腾讯云Cloud Studio提供了免…

阿里前端开发规范

文章目录 1. 为什么前端写代码要规范&#xff1f;一、代码规范的必要性二、 规范带来的好处 2. 资源一、推荐 1. 为什么前端写代码要规范&#xff1f; 一、代码规范的必要性 可维护性 统一的代码风格便于理解和修改减少代码维护成本降低项目交接难度 团队协作 提高团队开发效…

Linux 小火车

1.添加epel软件源 2.安装sl 3. 安装完成后输入&#xff1a; sl

高效流式大语言模型(StreamingLLM)——基于“注意力汇聚点”的突破性研究

论文地址&#xff1a;https://arxiv.org/pdf/2309.17453 github地址&#xff1a;https://github.com/mit-han-lab/streaming-llm 1. 研究背景与挑战 随着大语言模型&#xff08;LLMs&#xff09;在对话系统、文档摘要、代码补全和问答等领域的广泛应用&#xff0c;如何高效且准…

STM32-时钟树

STM32-时钟树 时钟 时钟

日志收集Day007

1.配置ES集群TLS认证: (1)elk101节点生成证书文件 cd /usr/share/elasticsearch ./bin/elasticsearch-certutil cert -out config/elastic-certificates.p12 -pass "" --days 3650 (2)elk101节点为证书文件修改属主和属组 chown elasticsearch:elasticsearch con…

使用Python和Qt6创建GUI应用程序---GUI的一个非常简短的历史

GUI的一个非常简短的历史 图形用户界面有着悠久而可敬的历史&#xff0c;可以追溯到20世纪60年代。斯坦福大学的NLS&#xff08;在线系统&#xff09;引入了鼠标和Windows概念于1968年首次公开展示。接下来是施乐PARC的Smalltalk系统GUI 1973&#xff0c;这是最现代的基础通用g…

如何建设一个企业级的数据湖

建设一个企业级的数据湖是一项复杂且系统化的工程&#xff0c;需要从需求分析、技术选型、架构设计到实施运维等多个方面进行综合规划和实施。以下是基于我搜索到的资料&#xff0c;详细阐述如何建设企业级数据湖的步骤和关键要点&#xff1a; 一、需求分析与规划 明确业务需…

xxl-job分布式定时任务

1 前言 1.1 业务场景 业务数据同步 ( 线上数据同步到线下&#xff0c;新平台老平台数据的同步 ) &#xff0c;消息通知&#xff0c;业务数据的补偿。 1.2 什么是定时任务 定时任务是指基于给定的时间点&#xff0c;给定的时间间隔或者给定执行次数自动的执行程序。 任务调度…

FLTK - FLTK1.4.1 - demo - adjuster.exe

文章目录 FLTK - FLTK1.4.1 - demo - adjuster.exe概述笔记根据代码&#xff0c;用fluid重建一个adjuster.fl 备注 - fluid生成的代码作为参考代码好了修改后可用的代码END FLTK - FLTK1.4.1 - demo - adjuster.exe 概述 想过一遍 FLTK1.4.1的demo和测试工程&#xff0c;工程…

Cursor的简单使用

目录 一、下载与配置 1.1、下载 1.2、汉化 1.3、模型选择 1.4、规则设置 二、Chat&#xff08;聊天&#xff09;和Composer&#xff08;编写助手&#xff09; 三、快捷键 3.1、tab(代码自动补全) 3.2、CtrlL、CtrlI 3.3、系列 3.4、预防、检测、回滚 四、无限登录 …

剥离情绪的内耗

情绪的内耗&#xff0c;指的是我们内心对于某些情绪的过度反应、反复纠结&#xff0c;或者对情感的压抑所产生的心理消耗。这种内耗通常会让我们感到疲惫、焦虑、无力&#xff0c;甚至影响到我们的行为和决策。要真正剥离情绪的内耗&#xff0c;核心在于如何认识、接受并合理处…

android的gradle

资料&#xff1a; GitHub - ChenSWD/CopyGradleInAction: 备份《Gradle IN Action》书中的源码&#xff0c;添加了部分注释 //github上一个开源项目&#xff0c;外加pdf书 Gradle User Manual gradle官网 讲的挺好的博客 Gradle之重新认识Gradle(项目结构、命令行、tas…

Python 之 Excel 表格常用操作

示例文件 test.xlsx 将各个表单拆分成单独的 Excel 文件 import os.pathimport openpyxl import pandasdef handle_excel(file_path):dirname os.path.dirname(file_path)basename os.path.basename(file_path).split(".")[0]wb openpyxl.load_workbook(file_pat…

【C语言系列】深入理解指针(4)

深入理解指针&#xff08;4&#xff09; 一、回调函数是什么&#xff1f;二、qsort使用举例2.1使用qsort函数排序整型数据2.2使用qsort排序结构数据 三、qsort函数的模拟实现四、总结 一、回调函数是什么&#xff1f; 回调函数就是一个通过函数指针调用的函数。 如果你把函数的…

计算机网络 (56)交互式音频/视频

一、定义与特点 定义&#xff1a;交互式音频/视频是指用户使用互联网和其他人进行实时交互式通信的技术&#xff0c;包括语音、视频图像等多媒体实时通信。 特点&#xff1a; 实时性&#xff1a;音频和视频数据是实时传输和播放的&#xff0c;用户之间可以进行即时的交流。交互…

FFmpeg 头文件完美翻译之 libavcodec 模块

前言 众所周知&#xff0c;FFmpeg 的代码开发上手难度较高&#xff0c;源于官方提供的文档很少有包含代码教程相关的。要想熟练掌握 FFmpeg 的代码库开发&#xff0c;需要借助它的头文件&#xff0c;FFmpeg 把很多代码库教程都写在头文件里面。因此&#xff0c;熟读头文件的内…

组件框架漏洞

一.基础概念 1.组件 定义&#xff1a;组件是软件开发中具有特定功能或特性的可重用部件或模块&#xff0c;能独立使用或集成到更大系统。 类型 前端 UI 组件&#xff1a;像按钮、下拉菜单、导航栏等&#xff0c;负责构建用户界面&#xff0c;提升用户交互体验。例如在电商 AP…