news 2026/9/23 19:55:01

纳什均衡计算全解析:MATLAB支持枚举与线性规划求解双矩阵博弈

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
纳什均衡计算全解析:MATLAB支持枚举与线性规划求解双矩阵博弈

简介:纳什均衡计算与MATLAB实现是博弈论学习和应用中的常见难点,这份资料包恰好提供了从理论到代码的系统参考。资源共6个文件,包括4个.m源码文件、1个txt计算说明和1个pdf理论文档,整体仅424KB,结构紧凑,可快速定位所需内容。源码覆盖博弈矩阵构建、纯策略与混合策略均衡求解,并通过优化函数或自定义迭代完成计算;txt文件给出公式推导与关键步骤,pdf则补充理论背景、实例分析和可能的应用方向。对于纯策略均衡不存在的情形,资料也涉及概率分布下的混合策略求解思路,能帮助读者理解期望收益最大化与不动点迭代的核心逻辑。已有817人学习使用,适合经济学、管理学或社会科学方向的学生完成课程作业,也可供研究人员在复杂系统决策分析中快速搭建纳什均衡计算工具。

1. 双矩阵博弈的纳什均衡公式与 MATLAB 计算路径

经常遇到这种咨询:教材上纳什均衡定义很简洁,真要把 A、B 两个支付矩阵丢进 MATLAB 算出均衡策略,反而不知道从哪下手。原因在于,混合策略均衡不是函数极值点,而是满足“支撑条件”的不动点:行玩家把正概率放在最优反应集内,列玩家同理。纯策略均衡可以逐点比较枚举,混合策略则需要解一组带支撑集约束的方程组。接下来从定义公式出发,给出支持枚举算法和零和博弈线性规划两套 MATLAB 源码,并说明数值容差、退化博弈和验证技巧,帮你在这个经典问题上彻底摆脱“调包不调参”的状态。

2. 支持枚举算法:从支撑条件到 MATLAB 函数实现

2.1 混合策略均衡的支撑条件

双矩阵博弈中,行玩家支付矩阵 A 为 m×n,列玩家矩阵 B 同样为 m×n。混合策略用概率向量 x 和 y 表示,期望支付为 xᵀAy 与 xᵀBy。纳什均衡的不等式定义对计算不友好,工程实现时更常用的是支撑条件(support condition):对行玩家,若 xᵢ > 0,则 (Ay)ᵢ = maxₖ(Ay)ₖ;对列玩家,若 yⱼ > 0,则 (Bᵀx)ⱼ = maxₗ(Bᵀx)ₗ。

支撑条件的意义很简单:均衡策略没有理由把正概率放在收益更低的纯策略上。反过来,只要 x、y 满足上述条件,就一定构成纳什均衡。枚举法就建立在“如果均衡存在,其支撑集必然是某个 (S,T)”这一观察上。给定候选支撑集 S、T,设 k=|S|、l=|T|,则均衡策略和均衡支付 v、u 满足:

A(S,T)·y_T = v·1_k
B(S,T)ᵀ·x_S = u·1_l
∑x_S = 1,∑y_T = 1

这是一个 k+l+2 元线性方程组。v 和 u 是未知标量,不是预先给定的值。把等式和归一化条件全部拼进系数矩阵 M 即可求解。

2.2 求解函数:nash_from_support.m

function [x, y, v, u, ok] = nash_from_support(A, B, S, T, tol) % 给定候选支撑集 S、T,解线性方程组得到混合策略均衡候选 % 输入 A: m×n 行玩家支付矩阵,B: m×n 列玩家支付矩阵 % 输入 S、T: 支撑集索引,tol: 数值容差 if nargin < 5, tol = 1e-8; end k = numel(S); l = numel(T); M = zeros(k+l+2); % 系数矩阵 rhs = zeros(k+l+2, 1); % 右端项 % 行玩家支撑条件: A(S,T)*y_T - v = 0 M(1:k, k+1:k+l) = A(S,T); M(1:k, k+l+1) = -1; % 列玩家支撑条件: B(S,T)'*x_S - u = 0 M(k+1:k+l, 1:k) = B(S,T)'; M(k+1:k+l, k+l+2) = -1; % 概率归一化 M(k+l+1, 1:k) = 1; rhs(k+l+1) = 1; M(k+l+2, k+1:k+l) = 1; rhs(k+l+2) = 1; z = lsqminnorm(M, rhs); % 退化时也不直接报错 if norm(M*z - rhs) > 1e-6 % 残差过大说明该支撑集无解 ok = false; x = []; y = []; v = []; u = []; return; end xS = z(1:k); yT = z(k+1:k+l); v = z(k+l+1); u = z(k+l+2); if any(xS < -tol) || any(yT < -tol) % 概率不允许负数 ok = false; return; end x = zeros(size(A,1),1); y = zeros(size(A,2),1); x(S) = max(xS, 0); y(T) = max(yT, 0); % 支撑集外策略收益不得高于均衡支付 if max(A*y - v) > tol || max(B'*x - u) > tol ok = false; return; end ok = true; end

这段代码的要点是:M 矩阵前 k 行把“行玩家支撑集内每个纯策略的期望收益相等且等于 v”写成等式;接着 l 行对应列玩家;最后两行是概率归一化。用 lsqminnorm 而不是反斜杠运算符,是因为退化博弈中 M 可能奇异,lsqminnorm 会返回最小范数解,配合残差检查比直接报错更实用。支撑集外检查用max(...) > tol而不是any(abs(...) > tol),因为定义只要求“不高于”,允许出现小于 v 的偏差。

2.3 主循环:nash_support_all.m

function eqs = nash_support_all(A, B, tol) % 遍历全部非空支撑集,返回所有纳什均衡 if nargin < 3, tol = 1e-8; end [m, n] = size(A); eqs = struct('x', {}, 'y', {}, 'v', {}, 'u', {}); for sm = 1:2^m-1 for tn = 1:2^n-1 S = find(bitget(sm, 1:m)); % 行支撑集 T = find(bitget(tn, 1:n)); % 列支撑集 [x, y, v, u, ok] = nash_from_support(A, B, S, T, tol); if ok eqs(end+1) = struct('x', x, 'y', y, 'v', v, 'u', u); end end end eqs = dedup_eqs(eqs, tol); end

主循环用 m、n 位的 bitmask 枚举所有非空子集,bitget(sm, 1:m)把整数拆成逻辑向量,再find取索引。这种做法比 nchoosek 循环快,而且代码直观。对 3×3 的课程设计题目,两层循环至多 7×7 次,毫秒级出结果;m=6、n=6 时约 63×63=3969 次线性求解,也就是秒级。去重部分用两两比较:

function eqs = dedup_eqs(eqs, tol) % 按策略向量距离合并重复均衡 keep = true(1, numel(eqs)); for i = 1:numel(eqs) for j = i+1:numel(eqs) if norm(eqs(i).x - eqs(j).x) < sqrt(tol) && ... norm(eqs(i).y - eqs(j).y) < sqrt(tol) keep(j) = false; end end end eqs = eqs(keep); end

去重阈值取 sqrt(tol) 而不是 tol,是因为迭代求解的误差在策略分量上会放大一倍量级。实际使用中如果发现同一均衡被返回两次,优先调整这个阈值,而不是全局放宽 tol。

算例用石头剪刀布:A = [0 1 -1; -1 0 1; 1 -1 0],B = -A。调用nash_support_all(A, B, 1e-10)后,唯一均衡为 x = y = (1/3, 1/3, 1/3)。同时枚举法也会把纯策略均衡检索出来,比如把 A 换成囚徒困境的支付矩阵 A = [-1 -3; 0 -2],B = [-1 0; -3 -2],返回的 eqs 里会有一个支撑集大小为 1 的均衡,对应双方都选背叛。一个函数同时覆盖两种均衡,这是支持枚举相对其他局部搜索算法最明显的优势。

3. 零和博弈线性规划:linprog 求解 maxmin 策略

3.1 零和博弈与线性规划约束

当 B = -A 时,博弈是零和的。行玩家的收益最大化问题可以写成 maxmin 形式:选择 x 使得最坏情况下的期望收益尽量大。设价值为 v,则约束是行玩家所有纯策略的期望收益不低于 v,加上概率归一化:

max v
s.t. Aᵀx ≥ v
∑xᵢ = 1,x ≥ 0

约束 Aᵀx ≥ v 不是 linprog 接受的标准形式,需要改写成 -Aᵀx + v ≤ 0。列玩家的问题对称:min u,s.t. Ay ≤ u,∑yⱼ = 1,y ≥ 0。两个问题分别求解后,理论上 v 与 u 相等,就是博弈的价值。

linprog 参数行玩家取值含义
f[zeros(m,1); -1]最小化 -v 等价于最大化 v
A_ineq, b_ineq[-A', ones(n,1)], zeros(n,1)每个纯策略收益 ≥ v
Aeq, beq[ones(1,m), 0], 1概率之和为 1
lb[zeros(m,1); -inf]x 非负,v 无下界

注意 A_ineq 的行数是列玩家的策略数 n,列数是 m+1。把 -A' 的负号漏掉是最常见的符号错误。

3.2 完整求解函数 zero_sum_nash.m

function [x, y, v] = zero_sum_nash(A) % 零和博弈纳什均衡:行玩家 maxmin,列玩家 minmax % 返回行策略 x、列策略 y、博弈价值 v [m, n] = size(A); opts = optimoptions('linprog', 'Display', 'off', 'Algorithm', 'dual-simplex'); % 行玩家:max v, 约束 -A'*x + v <= 0 f1 = [zeros(m,1); -1]; A1 = [-A', ones(n,1)]; b1 = zeros(n,1); Aeq1 = [ones(1,m), 0]; beq1 = 1; lb1 = [zeros(m,1); -inf]; sol1 = linprog(f1, A1, b1, Aeq1, beq1, lb1, [], opts); x = sol1(1:m); v = sol1(m+1); % 列玩家:min u, 约束 A*y - u <= 0 f2 = [zeros(n,1); 1]; A2 = [A, -ones(m,1)]; b2 = zeros(m,1); Aeq2 = [ones(1,n), 0]; beq2 = 1; lb2 = [zeros(n,1); -inf]; sol2 = linprog(f2, A2, b2, Aeq2, beq2, lb2, [], opts); y = sol2(1:n); end

Algorithm选择 dual-simplex 而不是默认的 interior-point,是因为支付矩阵经常退化,dual-simplex 在退化顶点上的稳定性更好,而且对中小规模问题没有性能劣势。如果只想求行玩家策略,可以不写列玩家那一段,但两个一起求能直接验证 v 是否等于列玩家的 u,作为正确性检查。零和博弈有限纯策略一定有解,所以这里没有对 exitflag 做判断;如果你把 A 换成非常大的稀疏矩阵,建议补一段 exitflag 检查,把求解失败信息抛出来。

3.3 与支持枚举法的一致性验证

把支持枚举法返回的均衡代入零和函数对比,可以同时排查两边代码的符号错误:

G = randn(5,5); G = G - G'; % 构造斜对称零和博弈 [x, y, v] = zero_sum_nash(G); eqs = nash_support_all(G, -G, 1e-8); x_enum = eqs(1).x; y_enum = eqs(1).y; disp([x, x_enum]); % 两列策略应一致 disp([v, x_enum' * G * y_enum]); % 价值也应一致

如果这里不一致,优先检查 A1 和 A2 的维度。一个快速定位技巧:临时把 lb 中 v 分量的下界改成 0,看求解器是否报不可行,报错位置通常能直接指出是哪个约束写反了。

4. 退化博弈与数值容差:让完整遍历不漏解

4.1 奇异方程组为什么不能直接用反斜杠

支持枚举法在理论上是完备的,但工程实现有个明显的坎:当候选支撑集对应退化博弈时,M 矩阵奇异,M \ rhs会直接报错。所谓退化,是指在某个均衡处有超过支撑集大小的纯策略同为最优反应,导致等号约束线性相关。典型例子是全部支付为 0 的矩阵,任意策略组合都是均衡,此时枚举任何 (S,T) 都会构造出奇异 M。

处理办法是第 2 章代码里的 lsqminnorm 加残差检查。但残差阈值 1e-6 不是通用值,它应该和支付矩阵的量级挂钩。常见做法是先归一化支付矩阵再做计算:

scale = max(1, max(abs(A(:)))); A = A / scale; B = B / scale;

归一化不改变均衡策略集合,但让 v、u 落在 [-1, 1] 量级,这时候固定用 1e-8 的容差即可。如果没有归一化就直接跑原始矩阵,不同量级下会误报“无解”,本质是容差设置不合理而不是算法错了。

4.2 退化均衡的扰动检查

完整枚举退化博弈时,同一个均衡往往对应多个支撑集,去重后依然可能残留数值噪声导致的假均衡。一般做法是用扰动法做第二轮验证:对支付矩阵加一个幅度很小的随机噪声,重新计算均衡,再回代原矩阵验证 regret。如果扰动后均衡消失或突变,说明原结果处在退化边界上,应标记为可疑,不要直接作为最终答案。

rng(1); A_pert = A + 1e-7 * randn(size(A)); eqs_pert = nash_support_all(A_pert, B, 1e-8); % 对每个扰动均衡,回代原始矩阵验证 for i = 1:numel(eqs_pert) eqs_pert(i).regret = nash_regret(A, B, eqs_pert(i).x, eqs_pert(i).y); end

扰动幅度 1e-7 与容差 1e-8 要配套:扰动过大,会把本来不存在的均衡“制造”出来;扰动过小,又不足以打破退化约束的奇异性。

4.3 linprog 报错的三类情况

零和博弈用 linprog 有三个高频报错。第一个是“未定义函数或变量 'linprog'”,说明当前 MATLAB 没有 Optimization Toolbox,用which linprog确认。没有工具箱时,小规模零和博弈可以退回第 2 章的支持枚举法。第二个是许可证相关的提示,比如许可证不允许使用 Optimization Toolbox,此时ver能看到已装工具箱列表,解决方式是申请包含该工具箱的许可证。第三个是求解终止但 v 明显不合理,通常不是求解器问题,而是 A1 少写了负号,比如把[-A', ones(n,1)]写成了[A', ones(n,1)],约束方向完全反了。

license('test', 'Optimization_Toolbox') % 返回 1 表示可用

这条命令值得在调试前先跑一次,直接把工具箱问题排除掉。

4.4 中文注释乱码与脚本编码

从压缩包解压出来的老代码经常带中文注释,用新版本 MATLAB 打开后注释变成乱码。主要原因是旧脚本是 ANSI/GBK 编码,MATLAB 2023a 及以后默认按 UTF-8 读取脚本,编码不匹配才会乱码。这和算法本身无关,但会干扰调试。做法是用任意文本编辑器把 .m 文件另存为 UTF-8,再重新打开;注意编辑器设置里不要把“自动猜测编码”关掉,否则打开 GBK 存档时会按 UTF-8 硬读。这个坑不处理,后面的代码逻辑再正确,注释也帮不上忙。

5. 均衡的 regret 验证与大规模博弈出路

5.1 regret 检验函数

数值方法返回的均衡必须做残差验证。均衡遗憾值(regret)定义为:行玩家所有纯策略中的最高期望收益减去混合策略期望收益,列玩家同样计算,二者取最大。不超过容差,才认为结果是数值意义上的均衡。

function reg = nash_regret(A, B, x, y) % 计算纳什均衡遗憾值,小于 1e-6 可认为收敛 v = x' * A * y; reg_row = max(A * y) - v; reg_col = max(B' * x) - x' * B * y; reg = max([reg_row, reg_col]); end

对支持枚举法返回的 eqs 逐项跑这个函数,任何一项超过 1e-6 就说明支撑集约束没满足,优先排查 lsqminnorm 残差阈值是否偏大。regret 的另一个用法是对比两种算法:同一个零和矩阵用枚举法和 linprog 各自求解,谁的 regret 更小,谁在当前矩阵上更可信。

5.2 蒙特卡洛抽查

regret 做的是顶点精确检查,蒙特卡洛负责边界抽查。用 rand 生成 1000 个随机混合策略,归一化到概率单纯形,逐个检查行扰动策略的收益是否超过 v + 1e-6,列扰动同理。两者都通过,均衡才适合写进报告或作为后续实验的基准。这套方法不依赖具体求解器,任何算法输出的结果都能用同一套代码验收。

5.3 规模判断与落盘

支持枚举法复杂度是 O(2^(m+n)),m+n 超过 16 建议直接换 Lemke-Howson 或复制者动态;零和博弈则继续用 linprog,没有这个规模瓶颈。判断标准:2^m * 2^n超过 1e6 就不要再跑 nash_support_all。所有均衡和对应 regret 值用writematrixsave落盘,课程设计报告引用数值时直接附上验证结果,比单独贴代码更有说服力。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/23 19:54:21

基于Django的Web安全渗透测试工具:模块化扫描与误报治理

简介&#xff1a;基于Python-Django构建的多功能Web安全渗透测试工具&#xff0c;集成漏洞检测、目录识别、端口扫描、指纹识别、域名探测、旁站探测与信息泄露检测等能力&#xff0c;形成从资产收集、信息收集到风险分析、漏洞验证的完整评估链路&#xff0c;适合安全测试人员…

作者头像 李华
网站建设 2026/9/23 19:48:35

Numba 使用 FAQ 全解:安装排障、编程技巧与性能优化实战指南

Numba 使用 FAQ 全解&#xff1a;安装排障、编程技巧与性能优化实战指南 【免费下载链接】numba NumPy aware dynamic Python compiler using LLVM 项目地址: https://gitcode.com/gh_mirrors/nu/numba 导读&#xff1a;本文以 Numba 官方用户手册的 FAQ 章节 为骨架&…

作者头像 李华
网站建设 2026/9/23 19:44:04

DeepSeek轻量级VRP模型:物流路径优化实战指南

简介&#xff1a;本资源是一份面向物流行业技术从业者与AI模型开发者的技术实践指南&#xff0c;聚焦DeepSeek大模型在路径优化场景的落地应用&#xff0c;解决传统物流中运输迂回、空驶率高、调度效率低等降本增效痛点。文档共26页PDF&#xff0c;完整覆盖从行业需求分析、数据…

作者头像 李华
网站建设 2026/9/23 19:41:55

Vibe-Trading Tushare new_share 新股接口实战指南

Vibe-Trading Tushare new_share 新股接口实战指南 【免费下载链接】Vibe-Trading "Vibe-Trading: Your Personal Trading Agent" 项目地址: https://gitcode.com/GitHub_Trending/vi/Vibe-Trading new_share 是 Tushare 的新股上市列表接口&#xff0c;Vibe-…

作者头像 李华
网站建设 2026/9/23 19:41:36

基于OpenCV的银行卡卡号识别:图像处理与模板匹配实战

简介&#xff1a;基于OpenCV的银行卡识别系统Python项目&#xff0c;面向计算机视觉初学者与金融科技开发者&#xff0c;提供从图像预处理、字符定位到识别的完整工程方案。资源共43个文件&#xff0c;压缩包10.31MB&#xff0c;包含10个Python脚本&#xff08;app.py、darknet…

作者头像 李华
网站建设 2026/9/23 19:40:53

洛谷基础篇zip校验与PDF转Markdown刷题全指南

简介&#xff1a;围绕《洛谷深入浅出程序设计竞赛&#xff08;基础篇&#xff09;》整理的源码与配套文档&#xff0c;面向正在备战程序设计竞赛、或按基础篇学习算法与数据结构的读者。压缩包共 91 个文件&#xff0c;以 87 个 C 源文件为主&#xff0c;另含少量使用说明、构建…

作者头像 李华