五子棋AI 算法——极大极小搜索

来源:互联网 发布:php 广告发布系统源码 编辑:程序博客网 时间:2024/06/10 08:40
计算机博弈(也称机器博弈),是一个挑战无穷、生机勃勃的研究领域,是人工智能领域的重要研究方向,是机器智能、兵棋推演、智能决策系统等人工智能领域的重要科研基础。机器博弈被认为是人工智能领域最具挑战性的研究方向之一。
        
        机器博弈的核心技术是博弈搜索算法

        零和博弈zero-sum game),又称零和游戏,与非零和博弈相对,是博弈论的一个概念,属非合作博弈。指参与博弈的各方,在严格竞争下,一方的收益必然意味着另一方的损失,博弈各方的收益和损失相加总和永远为“零”,双方不存在合作的可能。
        也可以说:自己的幸福是建立在他人的痛苦之上的,二者的大小完全相等,因而双方都想尽一切办法以实现“损人利己”。零和博弈的结果是一方吃掉另一方,一方的所得正是另一方的所失,整个社会的利益并不会因此而增加一分。

----------------------------------------------------------------------------------------------------------------------------------------------------

        最单纯的极大极小算法

        局面估价函数:我们给每个局面(state)规定一个估价函数值 f,评价它对于己方的有利程度。胜利的局面的估价函数值为 +oo,而失败的局面的估价函数值为–oo。

        Max 局面:假设这个局面轮到己方走,有多种决策可以选择,其中每种决策都导致一种子局面(sub-state)。由于决策权在我们手中,当然是选择估价函数值 f 最大的子局面,因此该局面的估价函数值等于子局面 f 值的最大值,把这样的局面称为 max 局面。

        Min 局面:假设这个局面轮到对方走,它也有多种决策可以选择,其中每种决策都导致一种子局面(sub-state)。但由于决策权在对方手中,在最坏的情况下,对方当然是选择估价函数值 f 最小的子局面,因此该局面的估价函数值等于子局面 f 值的最小值,把这样的局面称为 max 局面。

        终结局面:胜负已分(假设没有和局)



        假如有如上图的博弈树,设先手为 A ,后手为 B ;则 A 为 max 局面,B 为 min 局面。上图中 A 一开始有 2 种走法( w2 和 w3 ,w表示结点记号),它走 w2 还是 w3 取决于 w2 和 w3 的估价函数值f(),因为 A 是 max 局面,所以它会取 f(w2)  和 f(w3) 中大的那个,f(x) 怎么求呢?通常是以递归的方式对博弈树进行搜索,我们通常可以设定叶子结点局面的估价值。

        例如上图的搜索过程为 w1 --> w2 --> w4 ,然后回溯到 w1 --> w2 得到 f(w2) = 3 ,接着 w1 --> w2 --> w5 得到 f'(w2) = 1,因为 w2 在第二层,是 min 局面,所以它会选择得到的结果中小的那个,即用 f'(w2) 替代 f(w2) ,即 f(w2) = 1,接着 w1 --> w2 --> w6 得到 f'(w2) = 6 > f(w2) ,直接忽略。因此如果 A 往 w2 走的话将会得到一个估价值为 f(w2) = 1 的局面;类似地,如果往 w3 走的话将会得到一个估价值为 f(w3) = -3 的局面。而 A 是 max 局面,所以它会选择估价值大的走法,f(w2) = 1 > f(w3) = -3,因此它下一步走 w2。

        伪代码表示为:
[cpp] view plain copy
  1. // player = 1 表示轮到己方, player = 0 表示轮到对方  
  2. // cur_node 表示当前局面(结点)  
  3. maxmin(player, cur_node)  
  4. {  
  5.     if 达到终结局面  
  6.         return 该局面结点的估价值 f  
  7.     end  
  8.     if player == 1 // 轮到己方走  
  9.         best = -oo // 己方估价值初始化为 -oo  
  10.         for 每一种走法 do  
  11.             new_node = get_next(cur_node) // 遍历当前局面 cur_node 的所有子局面  
  12.             val = maxmin(player^1, new_node); // 把新产生的局面交给对方,对方返回一个该局面的估价值  
  13.             if val > best  
  14.                 best = val;  
  15.             end  
  16.         end  
  17.         return best;  
  18.     else // 轮到对方走  
  19.         best = +oo // 对方估价值初始化为 +oo  
  20.         for 每一种走法 do  
  21.             new_node = get_next(cur_node) // 遍历当前局面 cur_node 的所有子局面  
  22.             val = maxmin(player^1, new_node); // 把新产生的局面交给对方,对方返回一个该局面的估价值  
  23.             if val < best  
  24.                 best = val;  
  25.             end  
  26.         end  
  27.         return best;  
  28.     end  
  29. }  



        但是,实际问题中的所有局面所产生的博弈树一般都是非常庞大,非常庞大的多叉树~,并不能依靠暴力搜索来寻找最佳解法。因此需要用到一些剪枝手段。常用的比较初级的有 alpha-beta 剪枝。

        简单地说就是用两个参数 alpha 和 beta 表示当前局面的父局面的估价函数值 f()。如果当前局面为 min 局面,则需要借助父局面目前已求得的 f() (即 alpha)来判断是否需要继续搜索下去;如果当前局面为 max 局面,则需要借助父局面目前已求得的 f() (即 beta)。

        具体来说就是这样,举个例子:

        如果下图是某问题所产生的一颗博弈树,先手 A 为己方,后手 B 为对方。


        通过前面所述的方法,如果 A 走 w2 则可以得到这样的结果:



        alpha 初始值为 -oo,beta 初始值为 +oo;w1 在得到 f(w1) = 6 的整个过程中, alpha 和 beta 是这样变化的:w1 --> w2 -- > w5 得到 f'(w2) = 6 < f(w2) = +oo,即修改 f(w2) = f'(w2) = 6;同时修改 beta = 6 (因为 w2 的子局面是否需要剪枝依赖于 w2 的估价值 f(),而与 alpha 无关,故不需修改 alpha)。在正常搜索完 w1 --> w2 --> w6 后, w2 层把 beta = 6 返回给上一层 w1 的 alpha,即 w1 层的 alpha = 6,beta = +oo。


        
        接下来在 w3 里搜索就可以体现 alpha-beta 剪枝的效果;A 在选择 w3 走的时候同时把所得的 alpha 和 beta 传递下去,在经过 w1 --> w3 --> w7 得到 f(w3) = 4 (同时使 beta = 4)后,首先进行判断:如果 alpha > beta,则直接返回 beta = 4,没有必要再搜索 w8 和 w9。这是因为这个 alpha 是 w1 在走 w2 这条路时得到的一个估价值 f(w1),而 w3 是 min 局面,它会选择子局面 w7, w8, w9 中 f() 的值小的作为 f(w3),所以 w3 在得到 f(w3) = 4 后如果继续搜索 w8, w9,只会得到更小的值;w1 是 max 局面,它的 f() 要修改的条件是找到估价值比它更大的子局面,而 w1 目前已知的估价值 f(w1) = 6 比 f(w3) 要大,所以无论 w3 再怎么继续搜索下去,w1 都不会选 w3 作为下一步,所以没有必要搜索下去。这样就剪掉了 w8, w9 这两个分支,直接跳出 w3 进入 w4 继续搜索。另外一种情形也是类似的道理,这样就实现了有效的剪枝优化。

        伪代码表示为:
[cpp] view plain copy
  1. // player = 1 表示轮到己方, player = 0 表示轮到对方  
  2. // cur_node 表示当前局面(结点)  
  3. maxmin(player, cur_node, alpha, beta)  
  4. {  
  5.     if 达到终结局面  
  6.         return 该局面结点的估价值 f  
  7.     end  
  8.     if player == 1 // 轮到己方走  
  9.         for 每一种走法 do  
  10.             new_node = get_next(cur_node) // 遍历当前局面 cur_node 的所有子局面  
  11.             val = maxmin(player^1, new_node, alpha, beta); // 把新产生的局面交给对方,对方返回一个新局面的估价值  
  12.             if val > alpha  
  13.                 alpha = val;  
  14.             end  
  15.             if alpha > beta  
  16.                 return alpha;  
  17.             end  
  18.         end  
  19.         return alpha;  
  20.     else // 轮到对方走  
  21.         for 每一种走法 do  
  22.             new_node = get_next(cur_node) // 遍历当前局面 cur_node 的所有子局面  
  23.             val = maxmin(player^1, new_node, alpha, beta); // 把新产生的局面交给对方,对方返回一个新局面的估价值  
  24.             if val < beta   
  25.                 beta = val;  
  26.             end  
  27.             if alpha > beta  
  28.                 return beta;  
  29.             end  
  30.         end  
  31.         return beta;  
  32.     end  

原创粉丝点击