《算法的乐趣》——博弈树与棋类游戏

从这一篇文章开始,笔者开始了对《算法的乐趣》一书的学习。与以往笔者看的面向竞赛的算法数和经典教材不同,这本书接介绍的算法多为在现实生活中或者已经应用在生产实践当中的算法,比如说这篇文章所介绍的博弈树,就是前段时间非常火的人与AI的围棋大战的基础。

需要提前说明的一件事情是,由于本书当中的算法有非常好的应用与实践性,但是受笔者能力和经历所限,可能无法非常给出并分析算法的源代码,因此笔者在文章中介绍这些算法的时候,也主要以算法思想和伪代码为主,如果读者对某个算法的源代码感兴趣,可以留言笔者会将原作者所写的源码发送给你。

那么我们开始正文。

首先,我们来抛出一个问题,既IBM的深蓝打败国际象棋大师卡斯帕罗之后,也就在最近,Google的阿尔法狗又攻陷了人类智力的最后堡垒——围棋。那么我们一定会好奇,没有生命的计算机何以强大到如此地步能够在这些以智力称道的游戏中打败人类呢?那么接下来我们就要一一揭开棋类AI的神秘面纱。

博弈:

首先我们应该对我们要探讨的问题有充分的了解,不管是围棋还是国际象棋,他们本质上都能叫做“博弈”,那么什么是博弈呢?博弈可以理解为有限参与者进行有限策略选择的竞争性活动,比如下棋、打牌、竞技、战争等。而在这里我们将博弈更进一步简化,我们探讨一些简单的“二人零和、全信息、非偶然”的博弈。所谓零和,就是有输必有赢,最多出现平局,不会出现双赢的博弈;而“全信息”则指博弈过程双方对战局信息的了解是公开和透明的,即一种信息对称;而所谓“非偶然”,即规定博弈的参与者都是理性而聪明的。

初步了解了博弈的定义,我们从一个最简单的博弈开始探讨。

以井字棋游戏为例的极大极小值搜索算法:

首先来介绍井字棋游戏,非常类似五子棋,在一个3x3的方格上,双方轮流填充“x”和“o”,先使得3个“x”或“o”相连的一方获胜。

我们设玩家Max填充“x”,玩家Min填充“o”,Max先手。那么这里我们应该意识到的一个很重要的思想:计算机能够做出的一切看似“聪明”的策略,其实都是都是在较短时间内模拟出了所有棋局状态然后筛选出对AI最有利的状态。

我们来看这样一个图。

在这个图中,Player1其实就是上文中我们定义的Max,容易看到,这里是列出了前两步的所有状态的树结构,这种记录棋局状态的树状结构变叫做博弈树。

那么容易看到,我们可以顺着这个博弈树,得到所有的棋局状态,那么现在的问题是,假设我们已经得到了一个完全的博弈树,我们如何优化AI的决策呢?

这便是我们要介绍的极大极小值搜索算法,我们从Max即Player2的角度,设置一个权值w给博弈树中的每个节点,用来表征这个节点(某一种状态)对Max的有利程度,这个权值越大,表示这种状态对Max更有利。

那么我们在模拟对弈的过程中,对于第一层树也就是Max填充“x”,由上面的定义,显然Max倾向选择节点权值较大的节点,即w[1] = max(w[2],w[2],w[3]),因此我们需要找到博弈树第二层三个节点的权值,这便需要继续拓展博弈树的第三层。

而对于玩家Min来说,它显然倾向于选择对自己更有利的棋局状态,即w最小的节点,由此我们可知w[2] = min(w[5],w[6],w[7],w[8],w[9]),同时w[3]、w[4]也是类似的方法求解。

是否找到了规律化的模式呢?容易看到,这是一个递归算法,对于第i层落子的策略,不论对于Max还是Min,它都要基于第i+1层的所有状态,只不过不同的是,对于玩家Max,他要选择该节点所有子节点的权值最大值,而Min则要选择最小值。

最终,我们需要将博弈树拓展到叶节点,然后回溯回去得到应对各种情况的最有利策略。

基于对这种思想理解,我们不难写出下面极大极小值算法的伪代码。

int MiniMax(node,depth,isMaxPlayer)
{
     if(depth == 0)
          return Evaluate(node);
     int score = isMaxPlayer ? -INF : INF;
     for_each(node的子节点child_node)
     {
         int value = MiniMax(child_node,depth-1,!isMaxPlayer);
         if(isMaxPlayer)
              score=max(score,value);
         else
              score=min(score,value);
     }
}

其实我们能够看到,这种博弈树是基于搜索或者穷举或者dp的思想都可以,这不难理解,这是很基本的编程思维,虽然可以说这是设计棋类AI很核心的部分,但是这样就能打败人类了么?当然不能,这种算法设计模式面临的一个最大的问题是,博弈树的规模太过庞大,当时计算能力超强的计算机也无从招架,因此在真正的算法设计中,我们需要需要限制搜索深度和各种各样的剪枝如阿尔法贝塔剪枝、A*剪枝等算法来削减博弈树的规模,这便是我们下面要介绍的内容。

时间: 2024-10-05 23:56:43

《算法的乐趣》——博弈树与棋类游戏的相关文章

关于自走棋类游戏棋子搜索算法分析

最近自走棋类游戏非常风靡,从DOTA2的自走棋到LOL的云顶之弈 玩家在享受游戏快乐的同时,也被搜不来牌所烦恼,当然这也是麻将元素带来乐趣的所在 分析搜牌算法: 1.有一个牌库1¥棋子13个比如一共有30个就是13*30:加上2¥棋子3¥4¥5¥棋子,总个数=13*30+14*25+15*20+12*15+6*10=1280(个) (以上数据与棋子个数均为猜测估算,目的为分析算法内容,数据来源不真实) 2.在这个牌库中,每回合从牌库中抽取牌,而抽取牌需要根据当前棋手等级对应一定的搜索概率,例如:

针对明棋类游戏的策梅洛定理的每个人都能读懂的证明过程(下象棋、围棋、国际象棋等的必胜下法求解)

网上流传的博弈论策梅洛定理的中文证明资料,其证明都不是那么容易理解读懂.因此这里描述一下我的通俗一点的证明. 策梅洛定理:在有限步数内一定会结束的已经明确规则的明棋类游戏中(例如中国象棋.国际象棋),对于特定的任一局面,设A先下,B后下,那么要么A有必胜下法,要么B有必胜下法,要么A.B都有必和下法. 证明用的是归纳法. 把相邻的A走一步棋.B走一步棋的下法组合作为本证明中的一回合走法.证明对于有限n回合的棋类游戏局面(正规开局也算是一种局面),要么A有必胜下法(1),要么B有必胜下法(2),要

《算法的乐趣》前言中面试算法。

刚刚看了王晓华前辈在<算法的乐趣>一书的前言中提到了一个面试题: 有一个由若干正整数组成的数列,数列中中的每一个数都不超过32.已知数列中存在反复的数字.请给出一个算法找出这个数列中全部反复的数字. 我用java实现了一种方法: package com.wr.FindSameNum; public class FindSameNum { public static void main(String[] args){ int[] myInput = {1,2,3,4,5,32,5,15,14,2

《算法的乐趣》——华容道游戏

这一章来简单的介绍一下华容道游戏及如何利用算法来计算出其最优步数. 首先对于华容道游戏,我们来介绍一下它的规则. 有点类似于拼图,本质上这是一个5x4的矩阵,我们的目标就是让曹操(2x2)的矩阵从5x4的矩阵中的第5行的3.4列走出来. 游戏规则很简单,但是想要用最小的步数来完成就不那么容易了,这便是我们下面要着力解决的问题. 其实对于学过dfs和bfs的读者,可能已经想到解决的思路了,其实可以搜索所有的状态,然后形成状态树(这和笔者前面介绍的双人对局的博弈树是一个东西),在建树的过程中,我们设

卡马克算法(地图重复利用,跑酷类游戏)

----------------------------下面是理论知识-------------------------- 卡马克算法:由约翰·卡马克(John Carmack)开发的一种游戏地图处理方法,被广泛运用到2D卷轴式游戏和手机游戏中.约翰·卡马克:id Software创始人之一,技术总监.享誉世界的著名程序员,以卡马克算法和3D游戏引擎开发而闻名世界,被奉为游戏行业偶像.同时他也是个全面型的技术天才,现在致力于民用航天器开发,是民用航天器开发小组Armadillo Aerospac

DRL 教程 | 如何保持运动小车上的旗杆屹立不倒?TensorFlow利用A3C算法训练智能体玩CartPole游戏

本教程讲解如何使用深度强化学习训练一个可以在 CartPole 游戏中获胜的模型.研究人员使用 tf.keras.OpenAI 训练了一个使用「异步优势动作评价」(Asynchronous Advantage Actor Critic,A3C)算法的智能体,通过 A3C 的实现解决了 CartPole 游戏问题,过程中使用了贪婪执行.模型子类和自定义训练循环. 该过程围绕以下概念运行: 贪婪执行--贪婪执行是一个必要的.由运行定义的接口,此处的运算一旦从 Python 调用,就要立刻执行.这使得

1.1 算法的作用:猜价格游戏

最近在c语言网学习c语言和算法,这些随笔当作笔记,以方便自己研究查阅 猜价格游戏介绍: 首先出示一件价格在999元以内的商品,参与者要猜出这件商品的价格.在猜价格的过程中,主持人会根据参与者给出的价格,相应地给出“高了”或“低了”的提示 1 #include <stdio.h> 2 int main() 3 { 4 int oldprice,price=0,i=0; 5 printf("请首先设置商品的真实价格:"); 6 scanf("%d",&

算法习题---3.01猜数字游戏提示

一.题目 实现一个经典“猜数字”游戏.给定答案序列和用户猜的序列,统计有多少数字位置正确(A),有多少数字在两个序列都出现过但位置不对(B). 输入包含多组数据.每组输入第一行为序列长度n,第二行是答案序列,接下来是若干猜测序列,猜测序列全0时该组数据结束.n=0时输入结束. 二:样例输入: 4 //列数 1 3 5 5 //答案序列 1 1 2 3 //猜测序列--下面都是--直到结束序列 4 3 3 5 6 5 5 1 6 1 3 5 1 3 5 5 0 0 0 0 //结束序列 10 //

三个水桶等分8升水的问题 -《算法的乐趣》

智力题目 有三个容积分别为3升.5升.8升的水桶,其中容积为8升的水桶中装满了水,容积为3升和容积为5升的水桶都是空的.三个水桶都没有刻度,现在需要将大水桶中的8升水等分成两份,每份都是4升水,附加条件是只能这三个水桶,不能借助其他辅助容器. “恩,是的,这是一个很经典的问题.” “然而,我们并不能想全,不信请继续往下看.”答案 ”废话不多说,直接看方法吧.“第一种(7步) 将8L的水桶中的水,倒满5L的水桶,这时:8L水桶为3L.5L水桶为5L.3L水桶为0L 将5L的水桶中的水,倒满3L的水