算法——结合实例了解Minimax算法(极小化极大算法)
计算机科学中最有趣的事情之一就是编写一个人机博弈的程序。有大量的例子,最出名的是编写一个国际象棋的博弈机器。但不管是什么游戏,程序趋向于遵循一个被称为Minimax算法,伴随着各种各样的子算法在一块。本篇将简要介绍 minimax 算法,并通过实例分析帮助大家更好的理解。
一、概念
Minimax算法又名极小化极大算法,是一种找出失败的最大可能性中的最小值的算法。Minimax算法常用于棋类等由两方较量的游戏和程序,这类程序由两个游戏者轮流,每次执行一个步骤。我们众所周知的五子棋、象棋等都属于这类程序,所以说Minimax算法是基于搜索的博弈算法的基础。该算法是一种零总和算法,即一方要在可选的选项中选择将其优势最大化的选择,而另一方则选择令对手优势最小化的方法。
(1)Minimax是一种悲观算法,即假设对手每一步都会将我方引入从当前看理论上价值最小的格局方向,即对手具有完美决策能力。因此我方的策略应该是选择那些对方所能达到的让我方最差情况中最好的,也就是让对方在完美决策下所对我造成的损失最小。
(2)Minimax不找理论最优解,因为理论最优解往往依赖于对手是否足够愚蠢,Minimax中我方完全掌握主动,如果对方每一步决策都是完美的,则我方可以达到预计的最小损失格局,如果对方没有走出完美决策,则我方可能达到比预计的最悲观情况更好的结局。总之我方就是要在最坏情况中选择最好的。
二、实例分析
实例一、井字棋中的应用
- 游戏规则和局面评估:井字棋是在 3×3 的棋盘上进行的游戏,玩家轮流在空格中放置自己的棋子(假设一方为 X,另一方为 O),率先将自己的三个棋子连成一线(横、竖或斜)的玩家获胜。如果棋盘填满后仍未出现连成一线的情况,则为平局。我们可以为每个局面定义一个评估值:如果 X 获胜,评估值为 + 1;如果 O 获胜,评估值为 - 1;如果是平局,评估值为 0。
- 构建游戏树:以 X 玩家为先手开始构建游戏树。在根节点处,X 玩家有 9 种可能的落子位置,每个位置对应一个子节点。对于每个子节点,O 玩家又有 8 种可能的落子位置,依此类推,直到游戏结束(出现获胜局面或平局)。
- Minimax 算法执行过程
- 叶节点评估:当到达游戏结束的局面(叶节点)时,根据游戏结果赋予评估值,如 X 获胜则评估值为 + 1,O 获胜则为 - 1,平局为 0。
- 回溯计算:从叶节点开始回溯,假设当前轮到 X 玩家(Max 玩家)决策,在其所处的节点,它会选择子节点中评估值最大的那个作为自己的最优策略。若当前轮到 O 玩家(Min 玩家)决策,它会选择子节点中评估值最小的那个。比如,在某个中间节点,X 玩家的三个子节点评估值分别为 - 1、0、1,那么 X 玩家会选择评估值为 1 的子节点对应的走法。
| |
---|---|---| |
---|---|---| |
假设当前棋盘为空,X 玩家考虑第一步的走法。算法会模拟所有可能的后续情况:
- X 选择左上角:然后 O 可能有多种回应,假设 O 选择中心位置。接着 X 再走,可能形成不同局面,继续模拟下去直到游戏结束,得到一个评估值。
- X 选择其他位置:同样依次模拟后续 O 的走法和 X 的应对,直到得出所有以 X 第一步不同走法为根节点的子树的评估值。假设经过计算,X 选择左上角后最终的评估值最高,那么算法就会认为 X 的第一步最优走法是左上角。
局限性和改进方向
- 局限性:Minimax 算法的时间复杂度和空间复杂度较高,随着游戏树的深度和广度增加,计算量呈指数级增长。在复杂的游戏中,如国际象棋、围棋等,可能无法在合理时间内计算出所有可能。
- 改进方向:为了减少计算量,可以采用 α-β 剪枝技术,在搜索过程中剪掉一些不可能影响最终结果的分支。还可以结合启发式函数,对局面进行更快速的评估,减少不必要的搜索深度。
实例二、纸币面值最大化最小
题目:现在考虑这样一个游戏:有三个盘子A、B和C,每个盘子分别放有三张纸币。A放的是1、20、50;B放的是5、10、100;C放的是1、5、20。单位均为“元”。有甲、乙两人,两人均对三个盘子和上面放置的纸币有可以任意查看。
游戏分三步:
(1)甲从三个盘子中选取一个。
(2)乙从甲选取的盘子中拿出两张纸币交给甲。
(3)甲从乙所给的两张纸币中选取一张,拿走。
其中甲的目标是最后拿到的纸币面值尽量大,乙的目标是让甲最后拿到的纸币面值尽量小。
基本思路:一般解决博弈类问题的自然想法是将格局组织成一棵树,树的每一个节点表示一种格局,而父子关系表示由父格局经过一步可以到达子格局。Minimax也不例外,它通过对以当前格局为根的格局树搜索来确定下一步的选择。而一切格局树搜索算法的核心都是对每个格局价值的评价。
解题:下图是上述示例问题的格局树:

注意,由于示例问题格局数非常少,我们可以给出完整的格局树。这种情况下我可以找到Minimax算法的全局最优解。而真实情况中,格局树非常庞大,即使是计算机也不可能给出完整的树,因此我们往往只搜索一定深度,这时只能找到局部最优解。
我们从甲的角度考虑。其中正方形节点表示轮到我方(甲),而三角形表示轮到对方(乙)。经过三轮对弈后(我方-对方-我方),将进入终局。黄色叶结点表示所有可能的结局。从甲方看,由于最终的收益可以通过纸币的面值评价,我们自然可以用结局中甲方拿到的纸币面值表示终格局的价值。
下面考虑倒数第二层节点,在这些节点上,轮到我方选择,所以我们应该引入可选择的最大价值格局,因此每个节点的价值为其子节点的最大值:

这些轮到我方的节点叫做max节点,max节点的值是其子节点最大值。
倒数第三层轮到对方选择,假设对方会尽力将局势引入让我方价值最小的格局,因此这些节点的价值取决于子节点的最小值。这些轮到对方的节点叫做min节点。
最后,根节点是max节点,因此价值取决于叶子节点的最大值。最终完整赋值的格局树如下:

总结一下Minimax算法的步骤:
(1)首先确定最大搜索深度D,D可能达到终局,也可能是一个中间格局。
(2)在最大深度为D的格局树叶子节点上,使用预定义的价值评价函数对叶子节点价值进行评价。
(3)自底向上为非叶子节点赋值。其中max节点取子节点最大值,min节点取子节点最小值。
(4)每次轮到我方时(此时必处在格局树的某个max节点),选择价值等于此max节点价值的那个子节点路径。
在上面的例子中,根节点的价值为20,表示如果对方每一步都完美决策,则我方按照上述算法可最终拿到20元,这是我方在Minimax算法下最好的决策。格局转换路径如下图红色路径所示:

注意几点:
(1)真实问题一般无法构造出完整的格局树,所以需要确定一个最大深度D,每次最多从当前格局向下计算D层。
(2)因为上述原因,Minimax一般是寻找一个局部最优解而不是全局最优解,搜索深度越大越可能找到更好的解,但计算耗时会呈指数级膨胀。
(3)也是因为无法一次构造出完整的格局树,所以真实问题中Minimax一般是边对弈边计算局部格局树,而不是只计算一次,但已计算的中间结果可以缓存。
三、伪代码
def minimax(node, depth, is_maximizing):if depth == 0 or node是终止节点:return 评估值if is_maximizing:value = -∞for child in node的子节点:value = max(value, minimax(child, depth-1, False))return valueelse:value = +∞for child in node的子节点:value = min(value, minimax(child, depth-1, True))return value
四、关键点
-
评估函数:需准确反映局面优劣(如棋子价值、位置优势)。
-
效率优化:实际应用中需结合剪枝(如Alpha-Beta剪枝)减少计算量。
-
深度限制:受计算资源限制,通常设置搜索深度。
总结
Minimax算法通过模拟对手最优策略,为当前玩家提供最稳健的决策。其核心是交替最小化与最大化收益,确保在最坏情况下争取最好结果。虽然理论完备,但实际应用需结合启发式评估和优化策略以提高效率。
相关文章:
算法——结合实例了解Minimax算法(极小化极大算法)
计算机科学中最有趣的事情之一就是编写一个人机博弈的程序。有大量的例子,最出名的是编写一个国际象棋的博弈机器。但不管是什么游戏,程序趋向于遵循一个被称为Minimax算法,伴随着各种各样的子算法在一块。本篇将简要介绍 minimax 算法&#…...
cornerstone3D学习笔记-MPR
最近在研究如何利用cornerstone3D (v1.70.13) 来实现MPR功能,找到它的一个demo -- volumeBasic, 运行效果如下图 看了下主程序的示例代码,非常简单,可以说corestone3D这个库把很多细节都封装起来了,使得调用者可以很简单的快速实…...
向量数据库是什么?「向量数据库详解」
目录 向量数据库详解 一、定义与核心概念 二、核心技术与组件 三、应用场景 四、与传统数据库的对比 五、典型技术框架 六、优缺点分析 七、AI领域的最新应用案例 八、总结 向量数据库详解 一、定义与核心概念 向量数据库是专门用于存储、检索和处理向量数据的数据库…...
C++ Primer 函数匹配
欢迎阅读我的 【CPrimer】专栏 专栏简介:本专栏主要面向C初学者,解释C的一些基本概念和基础语言特性,涉及C标准库的用法,面向对象特性,泛型特性高级用法。通过使用标准库中定义的抽象设施,使你更加适应高级…...
Dav_笔记14:优化程序提示 HINTs -4
指定全局表提示 指定表的提示通常是指发生提示的DELETE,SELECT或UPDATE查询块中的表,而不是指语句引用的任何视图中的表。 如果要为显示在视图中的表指定提示,Oracle建议使用全局提示,而不是在视图中嵌入提示。 您可以使用包含具…...
功率因素和电费的关系
功率因数与电费之间存在直接的关系,具体体现在功率因数调整电费上。 功率因数调整电费的定义 功率因数调整电费是指根据用户功率因数的水平高低,对用户的电费进行减收或增收的费用。这种调整机制旨在鼓励用户提高功率因数,减少无功功率的消…...
桥接模式 Bridge Pattern
桥接模式Abstraction 和 Implementor 的理解 在图书馆看到一本 通过电商项目真正实战《贯穿设计模式》。拿起来翻到了 桥接模式,感觉味道不对,和我印象中不一样。 感谢这位同学提供的源码 贯穿设计模式-适配器模式桥接模式_-CSDN博客GitHub - WeiXiao…...
C# SpinLock 类 使用详解
总目录 前言 SpinLock 是 C# 中一种轻量级的自旋锁,属于 System.Threading 命名空间,专为极短时间锁竞争的高性能场景设计。它通过忙等待(自旋)而非阻塞线程来减少上下文切换开销,适用于锁持有时间极短(如…...
Ubuntu 安装 OpenCV (C++)
版本详情: Ubuntu: 22.04 5.15.0-133-generic gcc: 11.4.0 g: 11.4.0 OpenCV: 4.7.0 1. 卸载 OpenCV 进入原先编译 opencv 的 build 目录,在该目录下打开终端,执行以下代码(如果 build 已经删除了,可以重新编译一…...
推荐两个比较好用的流程图js库
React Flow 和 Logic Flow 是两个用于构建流程图的 JavaScript 库,适用于不同的场景和需求。以下是它们的简要介绍和对比: React Flow React Flow 是一个基于 React 的流程图库,专注于构建高度可定制的节点和边。它适用于需要复杂交互和数据…...
前端模板引擎
前言 正常渲染拿到数据后渲染,三步走:格式化数据、编译模板、渲染数据 如下例 <!DOCTYPE html><html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice…...
Linux /dev/null
/dev/null 是 Linux 和类 Unix 系统中一个特殊且非常有用的设备文件,也被称为空设备。下面为你详细介绍它的特点、用途和使用示例。 特点 写入丢弃:当向 /dev/null 写入数据时,这些数据会被立即丢弃,不会被保存到任何地方&#…...
ubuntu安装docker 无法拉取问题
sudo docker run hello-world [sudo] ubuntu 的密码: Unable to find image hello-world:latest locally docker: Error response from daemon: Get "https://registry-1.docker.io/v2/": context deadline exceeded (Client.Timeout exceeded while awai…...
深入理解Kubernetes:容器编排的中流砥柱
Kubernetes容器编排 在云原生技术蓬勃发展的当下,Kubernetes(简称K8s)已成为容器编排领域的事实标准,为现代应用的部署、管理与扩展提供了强大支持。 K8s的核心优势之一是其卓越的容器编排能力 在传统应用部署模式下,…...
长尾词SEO优化软件:企业官网流量提升的软件【实测】
搜索引擎流量中68%来自长尾关键词(数据来源:Ahrefs 2025),但83%企业仍困于「高价值长尾词难挖掘内容生产跟不上」的双重困境。当同行用智能工具批量布局「孕妇防辐射服哪个牌子好」等精准词时,手动分析数据的你可能还在…...
用自己的数据训练yolov11目标检测
文章目录 概要理论知识整体架构流程架构优化多任务支持多参数体量 操作实操环境配置数据准备数据标注数据放置路径 训练预测 概要 YOLOv11 是 Ultralytics 团队于 2024 年 9 月 30 日发布的最新目标检测模型,延续了 YOLO 系列实时推理特性,同时通过架构优…...
gsoap实现webservice服务
gsoap实现webservice服务 在实现Web服务时,使用gSOAP是一个很好的选择,因为它提供了强大的工具和库来创建SOAP和RESTful服务。gSOAP是一个C和C语言开发的库,它支持SOAP协议的各种版本,包括SOAP 1.1和SOAP 1.2。下面是如何使用gSO…...
相比于WebSocket,SSE更适合轻量级
一、 前言 项目首页有一个待办任务数量和消息提醒数量的展示(单向数据的展示 ),之前使用了定时器,每隔十秒钟发送一次请求到后端接口拿数据,这也就是我们常说的轮询做法。 1. 轮询的缺点 我们都知道轮询的缺点有几种…...
项目2 数据可视化--- 第十五章 生成数据
数据分析是使用代码来探索数据内的规律和关联。 数据可视化是通过可视化表示来 探索和呈现数据集内的规律。 好的数据可视化,可以发现数据集中未知的规律和意义。 一个流行的工具是Matplotlib,他是一个数据绘图库; 还有Plotly包ÿ…...
【Maven私服配置】
Maven私服配置 对于一些中央的pom,应该配置对应的mirror镜像访问 <mirrors> <mirror> <id>alimaven</id> <name>aliyun maven</name> <url>http://maven.aliyun.com/nexus/content/groups/public/</url> <mirr…...
QT (四)模型/视图 QFileSystemModel,QStringListModel,QStandardItemModel
思考:QTableWidget 在某种程度上可以等价为QStandardItemModel,同理,其他的功能也有类似的等价,但是以当前的QTableWidget 和QStandardItemModel为例的话,两者都是用于实现建立表格的相关组件,只不过QStand…...
BSD实现:单播
分用单播数据报 如果程序执行到这里,说明程序并没有执行多播操作,那么大概率是单播。 维护缓存指针 udp_last_inpcb是上一次接收数据报的端口的控制块指针,维护该指针的依据是许多程序往往具有时间局部性,也就是:经…...
. Unable to find a @SpringBootConfiguration(默认软件包中的 Spring Boot 应用程序)
解决: 新建一个包即可 问题: 默认软件包中的 Spring Boot 应用程序。 原因: 默认包的定义 : 如果一个 Java 类没有使用 package 声明包名,则该类会被放置在默认包中。Spring Boot 遵循 Java 的包管理约定ÿ…...
【前端知识】浏览器兼容方案polyfill
浏览器兼容方案polyfill 什么是 Polyfill?Polyfill 的作用Polyfill 的工作原理1. **特性检测**2. **加载 Polyfill**3. **模拟实现** Polyfill 的常见场景Polyfill 的使用方式Polyfill 的优缺点优点缺点 常见的 Polyfill 库总结 什么是 Polyfill? Polyf…...
互信息的定义与公式
互信息 定义公式 从条件熵中我们知道,当获取的信息和要研究的食物”有关系时“,这些信息才能帮助我们消除不确定性。如何衡量获取信息和要研究事物“有关系”呢?比如常识告诉我们,一个随机事件“今天深圳下雨”和另一个随机事件“…...
(算法基础——树)——python树结构使用指南
1. 树的定义与实现 树是一种非线性数据结构,常用于解决层次化数据问题(如路径搜索、二叉树遍历等)。以下是树的两种常见实现方式: (1) 类(Class)实现 class TreeNode:def __init__(self, val0, leftNone…...
【小白学AI系列】NLP 核心知识点(七)Embedding概念介绍
Embedding(嵌入) 是自然语言处理(NLP)中非常重要的概念。简单来说,embedding 是一种将离散的、稀疏的、不可直接计算的对象(比如词、字符或句子)转换为 密集的、连续的向量表示 的技术。 这个向…...
Android adb测试常用命令大全
目录 一、查看最上层成activity名字: 二、查看Activity的任务栈: 三、获取安装包信息 四、性能相关 1、显示CPU信息 : 2、查看CPU使用信息 3、内存信息(meminfo package_name or pid 使用程序的包名或者进程id显示内存信息) 4、电量信…...
FRRouting配置与OSPF介绍,配置,命令,bfd算法:
文章目录 1、frrouting的配置:2、ospf2.1、检测和维护邻居关系2.2、ospfDR和BDR2.3、odpf邻居表2.4、ospf常用命令2.5、bfd配置 1、frrouting的配置: sudo service zebra start sudo service ospfd start telnet localhost 2604 en configure termina…...
【MyBatis】预编译SQL与即时SQL
目录 1. 以基本类型参数为例测试#{ }与${ }传递参数的区别 1.1 参数为Integer类型 1.2 参数为String类型 2. 使用#{ }传参存在的问题 2.1 参数为排序方式 2.2 模糊查询 3. 使用${ }传参存在的问题 3.1 SQL注入 3.2 对比#{ } 与 ${ }在SQL注入方面存在的问题 3.3 预编译…...
