五子棋AI算法第三篇-Alpha Beta剪枝

剪枝是必须的

  上一篇讲了极大极小值搜索,其实单纯的极大极小值搜索算法并没有实际意义。

  可以做一个简单的计算,平均一步考虑 50 种可能性的话,思考到第四层,那么搜索的节点数就是 50^4 = 6250000,在我的酷睿I7的电脑上一秒钟能计算的节点不超过 5W 个,那么 625W 个节点需要的时间在 100 秒以上。电脑一步思考 100秒肯定是不能接受的,实际上最好一步能控制在 5 秒 以内。

  顺便说一下层数的问题,首先思考层数必须是偶数。因为奇数节点是AI,偶数节点是玩家,如果AI下一个子不考虑玩家防守一下,那么这个估分明显是有问题的。 
然后,至少需要进行4层思考,如果连4四层都考虑不到,那就是只看眼前利益,那么棋力会非常非常弱。 如果能进行6层思考基本可以达到随便赢普通玩家的水平了(普通玩家是指没有专门研究过五子棋的玩家,棋力大约是4层的水平)。

Alpha Beta 剪枝原理

  Alpha Beta 剪枝算法的基本依据是:棋手不会做出对自己不利的选择。依据这个前提,如果一个节点明显是不利于自己的节点,那么就可以直接剪掉这个节点。

前面讲到过,AI会在MAX层选择最大节点,而玩家会在MIN层选择最小节点。那么如下两种情况就是分别对双方不利的选择:

在MAX层,假设当前层已经搜索到一个最大值 X, 如果发现下一个节点的下一层(也就是MIN层)会产生一个比X还小的值,那么就直接剪掉此节点。

  解释一下,也就是在MAX层的时候会把当前层已经搜索到的最大值X存起来,如果下一个节点的下一层会产生一个比X还小的值Y,那么之前说过玩家总是会选择最小值的。也就是说这个节点玩家的分数不会超过Y,那么这个节点显然没有必要进行计算了。

通俗点来讲就是,AI发现这一步是对玩家更有利的,那么当然不会走这一步。

在MAX层,假设当前层已经搜索到一个最大值 X, 如果发现下一个节点的下一层(也就是MIN层)会产生一个比X还小的值,那么就直接剪掉此节点。

  这个是一样的道理,如果玩家走了一步棋发现其实对AI更有利,玩家必定不会走这一步。

  下面图解说明,懒得画图,直接用wiki上的图:

  如上图所示,在第二层,也就是MIN层,当计算到第三个节点的时候,已知前面有一个3和一个6,也就是最小值为3。 在计算第三个节点的时候,发现它的第一个孩子的结果是5,因为它的孩子是MAX节点,而MAX节点是会选择最大值的,那么此节点的值不会比5小,因此此节点的后序孩子就没有必要计算了,因为这个节点不可能小于5,而同一层已经有一个值为3的节点了。

  其实这个图里面第三层分数为7的节点也是不需要计算的。

  这是 MAX 节点的剪枝,MIN节点的剪枝也是同样的道理,就不再讲了。 Alpha Beta 剪枝的 Alpha 和 Beta 分别指的是MAX 和 MIN节点。

代码实现

虽然原理说了很多,但是其实代码的实现特别简单。

对max和min函数都增加一个 alpha 和 beta 参数。在 max 函数中如果发现一个子节点的值大于 alpha,则不再计算后序节点,此为 Alpha 剪枝。在min 函数中如果发现一个子节点的值小于 beta,则不再计算后序节点,此为 Beta剪枝。

代码实现如下:

 1 var min = function(board, deep, alpha, beta) {
 2   var v = evaluate(board);
 3   total ++;
 4   if(deep <= 0 || win(board)) {
 5     return v;
 6   }
 7
 8   var best = MAX;
 9   var points = gen(board, deep);
10
11   for(var i=0;i<points.length;i++) {
12     var p = points[i];
13     board[p[0]][p[1]] = R.hum;
14     var v = max(board, deep-1, best < alpha ? best : alpha, beta);
15     board[p[0]][p[1]] = R.empty;
16     if(v < best ) {
17       best = v;
18     }
19     if(v < beta) {  //AB剪枝
20       ABcut ++;
21       break;
22     }
23   }
24   return best ;
25 }
26
27
28 var max = function(board, deep, alpha, beta) {
29   var v = evaluate(board);
30   total ++;
31   if(deep <= 0 || win(board)) {
32     return v;
33   }
34
35   var best = MIN;
36   var points = gen(board, deep);
37
38   for(var i=0;i<points.length;i++) {
39     var p = points[i];
40     board[p[0]][p[1]] = R.com;
41     var v = min(board, deep-1, alpha, best > beta ? best : beta);
42     board[p[0]][p[1]] = R.empty;
43     if(v > best) {
44       best = v;
45     }
46     if(v > alpha) { //AB 剪枝
47       ABcut ++;
48       break;
49     }
50   }
51   return best;
52 }

  按照wiki上的说法,优化效果应该达到 1/2 次方,也就是能优化到 50^2 = 2500 左右,实际我测试的时候并没有这么理想。不过节点数也不到之前的十分之一,平均大约每一步计算 50W 个节点,需要时间在10秒左右。相比之前的600W节点已经有了极大的提升。

  不过即使经过了Alpha Beta 剪枝,思考层数也只能达到四层,也就是一个不怎么会玩五子棋的普通玩家的水平。而且每增加一层,所需要的时间或者说计算的节点数量是指数级增加的。所以目前的代码想计算到第六层是很困难的。

我们的时间复杂度是一个指数函数 M^N,其中底数M是每一层节点的子节点数,N 是思考的层数。我们的剪枝算法能剪掉很多不用的分支,相当于减少了 N,那么下一步我们需要减少 M,如果能把 M 减少一半,那么四层平均思考的时间能降低到 0.5^4 = 0.06 倍,也就是能从10秒降低到1秒以内。

  而这个M是怎么来的? 其实 M 就是函数 gen 返回的那些可选的空位。其实gen函数有很大的优化空间,而这个优化后的gen函数其实就是启发式搜索函数。

时间: 2024-10-05 05:58:28

五子棋AI算法第三篇-Alpha Beta剪枝的相关文章

五子棋AI算法第二篇-极大极小值搜索算法

AI实现的基本思路-极大极小值搜索算法 五子棋看起来有各种各样的走法,而实际上把每一步的走法展开,就是一颗巨大的博弈树.在这个树中,从根节点为0开始,奇数层表示电脑可能的走法,偶数层表示玩家可能的走法. 假设电脑先手,那么第一层就是电脑的所有可能的走法,第二层就是玩家的所有可能走法,以此类推. 我们假设平均每一步有50种可能的走法,那么从根节点开始,往下面每一层的节点数量是上一层的 50被,假设我们进行4层思考,也就是电脑和玩家各走两步,那么这颗博弈树的最后一层的节点数为 50^4 = 625W

五子棋AI算法

之前说想写一些比较大型的算法,想了半天,还是觉得写五子棋的AI比较合适.一则因为自己研究过这个,有一些基础,二则尽管现在网上有很多五子棋AI算法的资料,但是确实都有些晦涩难懂.就想着借这个机会,凭自己的理解,尽量的讲通俗一些.然而,这个算法确实有些复杂,想要通俗一些需要较大的篇幅,一篇博客难以讲完,这里就分很多个章节,一点一点的深入探讨.为了让文章更加通俗一些,我会略去一部分很简单但是占用篇幅很长的代码,改为用几行注释说明. 框架的搭建 首先,我们计划是做一个五子棋AI,也就是说让玩家和这个AI

五子棋AI算法-迭代加深

前面讲到了算杀,其实在算杀之前应该讲一下迭代加深.因为这些文章是我边做边写的一些笔记,所以顺序上可能不是那么严谨. 按照前面的所有算法实现之后(当然不包括算杀),会发现一个比较严重的问题,就是电脑在自己已经胜券在握的情况下(有双三之类的棋可以走),竟然会走一些冲四之类的棋来调戏玩家.这种走法出现的本质就是因为现在的AI只比较最终结果,并没有考虑到路径长短.所以很容易出现在6层搜索到一个双三,其实在4层的时候也有一个双三,因为分数一样,AI会随机选择一个走法.就导致了明明可以两步赢的棋,AI非要走

C++alpha beta剪枝算法 实现4*4 tic-tac-toe

先上一张alpha beta原理图,一看就懂 代码有点长,主要是因为算评估值得时候用的是穷举. 玩家是1,电脑是2,可以选择难度以及先手. // // main.cpp // Tic-Tac-Toe // // Created by mac on 2017/4/2. // Copyright ? 2017年 mac. All rights reserved. // #include <iostream> #include <vector> #include <math.h&g

五子棋AI算法-算杀

关于剪枝问题 前面讲到的通过Alpha-Beta剪枝和启发式搜索可以将4层搜索的平均时间降低到1秒以下.只有这两个优化方式其实目前最多可以做到6层搜索,就是把AI和玩家各向后推算三步. 6层搜索的棋力其实相当弱,碰到经常玩五子棋的人基本都会输,更不要说对五子棋有研究的玩家.以目前的平均一个节点有50个子节点的搜索方式,把搜索效率提高50倍则可以增加一层搜索深度.而除了前面讲到的AlphaBeta剪枝和启发式搜索,其他的剪枝算法基本都是非安全剪枝.也就是说后序我们会使用一些非安全的剪枝算法来提升搜

五子棋AI算法-Zobrist

这个博客不是把五子棋算法研究透彻之后再写的,而是一边研究算法一边写代码,同时一边写博客,所以有些博文的顺序不太对,比如 Zobrist 其实应该放在算杀之前就讲的.不过这并没有大的影响,总体上的顺序是OK的. 另外,这一系列博客讲的五子棋代码其实是一个开源的项目,源码地址:https://github.com/lihongxun945/gobang 由于是边写代码边写博客,所以博客中的代码不是最新的,甚至是有bug的,所以源码请尽量参考上述开源项目中的代码.比如之前讲极大极小值搜索改为负极大值的

人机ai五子棋 ——五子棋AI算法之Java实现

人机ai五子棋 下载:chess.jar (可直接运行) 源码:https://github.com/xcr1234/chess 其实机器博弈最重要的就是打分,分数也就是权重,把棋子下到分数大的地方,我获胜的概率就更大. 而在下棋过程中,大部分的点的得分都很小,或者接近,因此无需对每一个点都打分,只需要在我方附近(进攻)或者敌方附近(防守)的几个点进行打分. 具体原理大家可以看源码中的注释,说明的很清楚. 参考 http://blog.csdn.net/pi9nc/article/details

五子棋AI算法-重构代码

为什么需要重构 之前的代码有很多松散的模块组合在一起.在把 Zobrist 集成进去时,会发现全部需要走棋的操作其实都需要进行一次 Zobrist 异或操作.另外在逻辑上,其实很多模块都是可以合并到同一个类的,所以这次把代码进行了一次大的重构.所以如果发现博客说的一些模块找不到了也是很正常的,因为大部分模块都被移到了 Board 类中. 这次重构主要的工作就是 把AI相关的代码分成了四个模块: Board ,所有和棋子相关的操作都在这里,包括打分,判断胜负,zobrist缓存,启发函数等. ne

象棋AI算法(一)

最近想做一个象棋游戏,但是AI把我难住了.这是这几天的成果: 象棋程序通过使用"搜索"函数来寻找着法.搜索函数获得棋局信息,然后寻找对于程序一方来说最好的着法. 一,最小-最大搜索Minimax Search 首先:最小与最大是相对的,且只针对一方,AI中即为有利于AI 象棋AI中的最小最大搜索:  简单来讲就是该AI走了,穷举这个过程中对于AI来说的最佳(最大)走法对于我来说最差(最小)的走法.而这个走法就是我们所要找的AI的最佳走法. 这个过程就跟你与别人下象棋时猜测对方走法然后下