极大极小值算法

极小极大的定义
Minimax算法 又名极小化极大算法,是一种找出失败的最大可能性中的最小值的算法(即最小化对手的最大得益)。通常以递归形式来实现。


Minimax算法常用于棋类等由两方较量的游戏和程序。该算法是一个零总和算法,即一方要在可选的选项中选择将其优势最大化的选择,另一方则选择令对手优势最小化的一个,其输赢的总和为0(有点像能量守恒,就像本身两个玩家都有1点,最后输家要将他的1点给赢家,但整体上还是总共有2点)。很多棋类游戏可以采取此算法,例如tic-tac-toe。


关于极小极大,更多的信息可参考以下文章:

Minimax(wikipedia)
Understanding The MiniMax Algorithm
Minimax Explained
最小最大原理与搜索方法

以TIC-TAC-TOE为例
tic-tac-toe就是我们小时候玩的井字棋(我这边习惯叫“井字过三关”),不熟悉的可以 参考wiki 。

一般X的玩家先下。设定X玩家的最大利益为正无穷(+∞),O玩家的最大利益为负无穷(-∞),这样我们称X玩家为MAX(因为他总是追求更大的值),成O玩家为MIN(她总是追求更小的值),各自都为争取自己的最大获益而努力。

现在,让我们站在MAX的立场来分析局势(这是必须的,应为你总不能两边倒吧,你喜欢的话也可以选择MIN)。由于MAX是先下的(习惯上X的玩家先下),于是构建出来的博弈树如下(前面两层):

极大极小值算法_第1张图片
 

MAX总是会选择MIN最大获利中的最小值(对MAX最有利),同样MIN也会一样,选择对自己最有利的(即MAX有可能获得的最大值)。有点难理解,其实就是自己得不到也不给你得到这样的意思啦,抢先把对对手有利的位置抢占了。你会看出,这是不断往下深钻的,直到最底层(即叶节点)你才能网上回溯,确定那个是对你最有利的。
具体过程会像是这么一个样子的:

极大极小值算法_第2张图片
 

但实际情况下,完全遍历一颗博弈树是不现实的,因为层级的节点数是指数级递增的:
层数   节点数
 0       1
 1       9
 2       72
 3       504
 4       3024
 5       15120
 6       60480
 7       181440
 8       362880

完全遍历会很耗时...一般情况下需要限制深钻的层数,在达到限定的层数时就返回一个估算值(通过一个启发式的函数对当前博弈位置进行估值),这样获得的值就不是精确的了(遍历的层数越深越精确,当然和估算函数也有一定关系),但该值依然是足够帮助我们做出决策的。于是,对耗时和精确度需要做一个权衡。一般我们限定其遍历的深度为6(目前多数的象棋游戏也是这么设定的)。

于是,我们站在MAX的角度,评估函数会是这样子的:

 

Java代码 复制代码  收藏代码
  1. static   final   int   INFINITY = 100 ;   // 表示无穷的值   
  2. static   final   int   WIN = +INFINITY ;   // MAX的最大利益为正无穷   
  3. static   final   int   LOSE = -INFINITY ;   // MAX的最小得益(即MIN的最大得益)为负无穷   
  4. static   final   int   DOUBLE_LINK = INFINITY / 2 ;   // 如果同一行、列或对角上连续有两个,赛点   
  5. static   final   int   INPROGRESS = 1 ;   // 仍可继续下(没有胜出或和局)   
  6. static   final   int   DRAW = 0 ;   // 和局   
  7. static   final   int [][] WIN_STATUS =   {   
  8.       {   012 },   
  9.       { 345 },   
  10.       { 678 },   
  11.       { 036 },   
  12.       { 147 },   
  13.       { 258 },   
  14.       { 048 },   
  15.       { 246   }   
  16. };   
  17. /**  
  18.  * 估值函数,提供一个启发式的值,决定了游戏AI的高低  
  19.  */  
  20. public   int   gameState( char []   board )   {   
  21.        int   result = INPROGRESS;   
  22.        boolean   isFull =   true ;   
  23.          
  24.        // is game over?   
  25.        for   ( int   pos = 0; pos < 9; pos++) {   
  26.              char   chess = board[pos];   
  27.              if   ( empty   == chess) {   
  28.                   isFull =   false ;   
  29.                    break ;   
  30.             }   
  31.       }   
  32.        // is Max win/lose?   
  33.        for   ( int [] status : WIN_STATUS) {   
  34.              char   chess = board[status[0]];   
  35.              if   (chess ==   empty ) {   
  36.                    break ;   
  37.             }   
  38.              int   i = 1;   
  39.              for   (; i < status.length; i++) {   
  40.                    if   (board[status[i]] != chess) {   
  41.                          break ;   
  42.                   }   
  43.             }   
  44.              if   (i == status.length) {   
  45.                   result = chess ==   x   ? WIN : LOSE;   
  46.                    break ;   
  47.             }   
  48.       }   
  49.        if   (result != WIN & result != LOSE) {   
  50.              if   (isFull) {   
  51.                    // is draw   
  52.                   result = DRAW;   
  53.             }   else   {   
  54.                    // check double link   
  55.                    // finds[0]->'x', finds[1]->'o'   
  56.                    int [] finds =   new   int [2];   
  57.                    for   ( int [] status : WIN_STATUS) {   
  58.                          char   chess =   empty ;   
  59.                          boolean   hasEmpty =   false ;   
  60.                          int   count = 0;   
  61.                          for   ( int   i = 0; i < status.length; i++) {   
  62.                                if   (board[status[i]] ==   empty ) {   
  63.                                     hasEmpty =   true ;   
  64.                               }   else   {   
  65.                                      if   (chess ==   empty ) {   
  66.                                           chess = board[status[i]];   
  67.                                     }   
  68.                                      if   (board[status[i]] == chess) {   
  69.                                           count++;   
  70.                                     }   
  71.                               }   
  72.                         }   
  73.                          if   (hasEmpty && count > 1) {   
  74.                                if   (chess ==   x ) {   
  75.                                     finds[0]++;   
  76.                               }   else   {   
  77.                                     finds[1]++;   
  78.                               }   
  79.                         }   
  80.                   }   
  81.                    // check if two in one line   
  82.                    if   (finds[1] > 0) {   
  83.                         result = -DOUBLE_LINK;   
  84.                   }   else   if   (finds[0] > 0) {   
  85.                         result = DOUBLE_LINK;   
  86.                   }   
  87.             }   
  88.       }   
  89.        return   result;   
  90. }   
 

基于这些,一个限定层数的实现是这样的:

 

Java代码 复制代码  收藏代码
  1. /**  
  2.  * 以'x'的角度来考虑的极小极大算法  
  3.  */  
  4. public   int   minimax( char [] board,   int   depth){   
  5.        int [] bestMoves =   new   int [9];   
  6.        int   index = 0;   
  7.          
  8.        int   bestValue = - INFINITY ;   
  9.        for ( int   pos=0; pos<9; pos++){   
  10.                
  11.              if (board[pos]== empty ){   
  12.                   board[pos] =   x ;   
  13.                      
  14.                    int   value = min(board, depth);   
  15.                    if (value>bestValue){   
  16.                         bestValue = value;   
  17.                         index = 0;   
  18.                         bestMoves[index] = pos;   
  19.                   } else  
  20.                    if (value==bestValue){   
  21.                         index++;   
  22.                         bestMoves[index] = pos;   
  23.                   }   
  24.                      
  25.                   board[pos] =   empty ;   
  26.             }   
  27.                
  28.       }   
  29.          
  30.        if (index>1){   
  31.             index = ( new   Random (System. currentTimeMillis ()).nextInt()>>>1)%index;   
  32.       }   
  33.        return   bestMoves[index];   
  34.          
  35. }   
  36. /**  
  37.  * 对于'x',估值越大对其越有利  
  38.  */  
  39. public   int   max( char [] board,   int   depth){   
  40.          
  41.        int   evalValue =   gameState (board);   
  42.          
  43.        boolean   isGameOver = (evalValue== WIN   || evalValue== LOSE   || evalValue== DRAW );   
  44.        if (depth==0 || isGameOver){   
  45.              return   evalValue;   
  46.       }   
  47.          
  48.        int   bestValue = - INFINITY ;   
  49.        for ( int   pos=0; pos<9; pos++){   
  50.                
  51.              if (board[pos]== empty ){   
  52.                    // try   
  53.                   board[pos] =   x ;   
  54.                      
  55.                    //   maximixing   
  56.                   bestValue = Math. max (bestValue, min(board, depth-1));   
  57.                      
  58.                    // reset   
  59.                   board[pos] =   empty ;   
  60.             }   
  61.                
  62.       }   
  63.          
  64.        return   evalValue;   
  65.          
  66. }   
  67. /**  
  68.  * 对于'o',估值越小对其越有利  
  69.  */  
  70. public   int   min( char [] board,   int   depth){   
  71.          
  72.        int   evalValue =   gameState (board);   
  73.          
  74.        boolean   isGameOver = (evalValue== WIN   || evalValue== LOSE   || evalValue== DRAW );   
  75.        if (depth==0 || isGameOver){   
  76.              return   evalValue;   
  77.       }   
  78.          
  79.        int   bestValue = + INFINITY ;   
  80.        for ( int   pos=0; pos<9; pos++){   
  81.                
  82.              if (board[pos]== empty ){   
  83.                    // try   
  84.                   board[pos] =   o ;   
  85.                      
  86.                    //   minimixing   
  87.                   bestValue = Math.min(bestValue, max(board, depth-1));   
  88.                      
  89.                    // reset   
  90.                   board[pos] =   empty ;   
  91.             }   
  92.                
  93.       }   
  94.          
  95.        return   evalValue;   
  96.          
  97. }   
 

Alpha-beta剪枝
另外,通过结合Alpha-beta剪枝能进一步优化效率。Alpha-beta剪枝顾名思义就是裁剪掉一些不必要的分支,以减少遍历的节点数。实际上是通过传递两个参数alpha和beta到递归的极小极大函数中,alpha表示了MAX的最坏情况,beta表示了MIN的最坏情况,因此他们的初始值为负无穷和正无穷。在递归的过程中,在轮到MAX的回合,如果极小极大的值比alpha大,则更新alpha;在MIN的回合中,如果极小极大值比beta小,则更新beta。当alpha和beta相交时(即alpha>=beta),这时该节点的所有子节点对于MAX和MIN双方都不会带来好的获益,所以可以忽略掉(裁剪掉)以该节点为父节点的整棵子树。

根据这一定义,可以很轻易地在上面程序的基础上进行改进:

 

Java代码 复制代码  收藏代码
  1. /**  
  2.  * 以'x'的角度来考虑的极小极大算法  
  3.  */  
  4. public   int   minimax( char [] board,   int   depth){   
  5.        int [] bestMoves =   new   int [9];   
  6.        int   index = 0;   
  7.          
  8.        int   bestValue = - INFINITY ;   
  9.        for ( int   pos=0; pos<9; pos++){   
  10.                
  11.              if (board[pos]== empty ){   
  12.                   board[pos] =   x ;   
  13.                      
  14.                    int   value = min(board, depth, - INFINITY , + INFINITY );   
  15.                    if (value>bestValue){   
  16.                         bestValue = value;   
  17.                         index = 0;   
  18.                         bestMoves[index] = pos;   
  19.                   } else  
  20.                    if (value==bestValue){   
  21.                         index++;   
  22.                         bestMoves[index] = pos;   
  23.                   }   
  24.                      
  25.                   board[pos] =   empty ;   
  26.             }   
  27.                
  28.       }   
  29.          
  30.        if (index>1){   
  31.             index = ( new   Random (System. currentTimeMillis ()).nextInt()>>>1)%index;   
  32.       }   
  33.        return   bestMoves[index];   
  34.          
  35. }   
  36. /**  
  37.  * 对于'x',估值越大对其越有利  
  38.  */  
  39. public   int   max( char [] board,   int   depth,   int   alpha,   int   beta){   
  40.          
  41.        int   evalValue =   gameState (board);   
  42.          
  43.        boolean   isGameOver = (evalValue== WIN   || evalValue== LOSE   || evalValue== DRAW );   
  44.        if (beta<=alpha){   
  45.              return   evalValue;   
  46.       }   
  47.        if (depth==0 || isGameOver){   
  48.              return   evalValue;   
  49.       }   
  50.          
  51.        int   bestValue = - INFINITY ;   
  52.        for ( int   pos=0; pos<9; pos++){   
  53.                
  54.              if (board[pos]== empty ){   
  55.                    // try   
  56.                   board[pos] =   x ;   
  57.                      
  58.                    //   maximixing   
  59.                   bestValue = Math. max (bestValue, min(board, depth-1, Math. max (bestValue, alpha), beta));   
  60.                      
  61.                    // reset   
  62.                   board[pos] =   empty ;   
  63.             }   
  64.                
  65.       }   
  66.          
  67.        return   evalValue;   
  68.          
  69. }   
  70. /**  
  71.  * 对于'o',估值越小对其越有利  
  72.  */  
  73. public   int   min( char [] board,   int   depth,   int   alpha,   int   beta){   
  74.          
  75.        int   evalValue =   gameState (board);   
  76.          
  77.        boolean   isGameOver = (evalValue== WIN   || evalValue== LOSE   || evalValue== DRAW );   
  78.        if (alpha>=beta){   
  79.              return   evalValue;   
  80.       }   
  81.        // try   
  82.        if (depth==0 || isGameOver || alpha>=beta){   
  83.              return   evalValue;   
  84.       }   
  85.          
  86.        int   bestValue = + INFINITY ;   
  87.        for ( int   pos=0; pos<9; pos++){   
  88.                
  89.              if (board[pos]== empty ){   
  90.                    // try   
  91.                   board[pos] =   o ;   
  92.                      
  93.                    //   minimixing   
  94.                   bestValue = Math.min(bestValue, max(board, depth-1, alpha, Math.min(bestValue, beta)));   
  95.                      
  96.                    // reset   
  97.                   board[pos] =   empty ;   
  98.             }   
  99.                
  100.       }   
  101.          
  102.        return   evalValue;   
  103.          
  104. }   
 
*这里对极小极大算法的实现只是其中一种可行性,实际上可能会看到很多种不同的实现方式,但道理是一样的。

使用开局库
同时,你会发现,这样做视乎还不够,特别在一开局。我们都知道,中心的位置是最好的,但是按照我们上面的算法,第一步确实随机的...这在深度受限制的情况下就更显得重要了。于是就引申出了开局库的概念,这是我在某个讲象棋AI的网上看到的,就是给初始的棋盘设定一些格局。

针对上面的例子,我们只要判断是否第一步棋,如果是则想办法让他选择中心的位置(4)。

在WIKI百科中找到一幅图也能作为TIC-TAC-TOE开局库的参考:

极大极小值算法_第3张图片
 

于是,估算函数会变成这样的(当然也可以在别的地方修改,只要合理):

 

Java代码 复制代码  收藏代码
  1. //开局时,每个位置的估值   
  2. static   final   int []   INITIAL_POS_VALUE   = {   
  3.       323,   
  4.       242,   
  5.       323  
  6. };   
  7. /**  
  8.  * 估值函数,提供一个启发式的值,决定了游戏AI的高低  
  9.  */  
  10. public   int   gameState ( char []   board ) {   
  11.        int   result =   INPROGRESS ;   
  12.        boolean   isFull =   true ;   
  13.        int   sum = 0;   
  14.        int   index = 0;   
  15.        // is game over?   
  16.        for ( int   pos=0; pos<9; pos++){   
  17.              char   chess = board[pos];   
  18.              if ( empty ==chess){   
  19.                   isFull =   false ;   
  20.             } else {   
  21.                   sum += chess;   
  22.                   index = pos;   
  23.             }   
  24.       }   
  25.          
  26.        // 如果是初始状态,则使用开局库   
  27.        boolean   isInitial = (sum== x ||sum== o );   
  28.        if (isInitial){   
  29.              return   (sum== x ?1:-1)*INITIAL_POS_VALUE[index];   
  30.       }   
  31.          
  32.        // is Max win/lose?   
  33.        for ( int [] status :   WIN_STATUS ){   
  34.              char   chess = board[status[0]];   
  35.              if (chess== empty ){   
  36.                    break ;   
  37.             }   
  38.              int   i = 1;   
  39.              for (; i<status.length; i++){   
  40.                    if (board[status[i]]!=chess){   
  41.                          break ;   
  42.                   }   
  43.             }   
  44.              if (i==status.length){   
  45.                   result = chess== x   ?   WIN   :   LOSE ;   
  46.                    break ;   
  47.             }   
  48.       }   
  49.          
  50.        if (result!= WIN   & result!= LOSE ){   
  51.                
  52.              if (isFull){   
  53.                    // is draw   
  54.                   result =   DRAW ;   
  55.             } else {   
  56.                    // check double link   
  57.                    // finds[0]->'x', finds[1]->'o'   
  58.                    int [] finds =   new   int [2];   
  59.                    for ( int [] status :   WIN_STATUS ){   
  60.                          char   chess =   empty ;   
  61.                          boolean   hasEmpty =   false ;   
  62.                          int   count = 0;   
  63.                          for ( int   i=0; i<status.length; i++){   
  64.                                if (board[status[i]]== empty ){   
  65.                                     hasEmpty =   true ;   
  66.                               } else {   
  67.                                      if (chess== empty ){   
  68.                                           chess = board[status[i]];   
  69.                                     }   
  70.                                      if (board[status[i]]==chess){   
  71.                                           count++;   
  72.                                     }   
  73.                               }   
  74.                         }   
  75.                          if (hasEmpty && count>1){   
  76.                                if (chess== x ){   
  77.                                     finds[0]++;   
  78.                               } else {   
  79.                                     finds[1]++;   
  80.                               }   
  81.                         }   
  82.                   }   
  83.                      
  84.                    // check if two in one line   
  85.                    if (finds[1]>0){   
  86.                         result = - DOUBLE_LINK ;   
  87.                   } else  
  88.                    if (finds[0]>0){   
  89.                         result =   DOUBLE_LINK ;   
  90.                   }   
  91.                      
  92.             }   
  93.                
  94.       }   
  95.          
  96.        return   result;   
  97.          
  98. }   

转载地址:http://univasity.iteye.com/blog/1170216

你可能感兴趣的:(极大极小值)