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

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

 

以TIC-TAC-TOE为例

tic-tac-toe就是我们小时候玩的井字棋

 

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

 

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



 

     上图中第0层为空棋盘,第1层是×方所有可能的步骤,第2层是〇方所有可能的步骤。在第1层,×方需要选择使其优势最大的选择,而在第2层,〇方则需要选择使×方优势最小即己方优势最大的选择。

     Minimax的含义就是极小化对手的最大利益,在上图中,在第2层〇方一定会选择使自己优势最大的选择,而对于×方需要做的就是选择〇方最大选择中的极小值。

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



 

 

但实际情况下,完全遍历一颗博弈树是不现实的,因为层级的节点数是指数级递增的:

层数   节点数

0       1
 1       9
 2       72
 3       504
 4       3024
 5       15120
 6       60480
 7       181440
 8       362880

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

 

代码基本思想:
1. 设博弈双方中一方为MAX,另一方为MIN。然后为其中的一方(计算机)找一个最佳走法。

2. 为了找到当前棋局的最优走法,需要对各个可能的走法所产生的后续棋局进行比较,同时也要考虑对方可能的走法,并对后续棋局赋予一定的权值(或者称之为分数)。也就是说,以当前棋局为根节点生成一棵博弈树,N步后的棋局作为树的叶子节点。同时从树根开始轮流给每层结点赋予Max和Min的称号

3. 用一个评估函数来分析计算各个后续棋局(即叶子节点)的权值,估算出来的分数为静态估值

4. 当端节点的估值计算出来后,再推算出父节点的得分。推算的方法是:对于处于MAX层的节点,选其子节点中一个最大的得分作为父节点的得分,这是为了使自己在可供选择的方案中选一个对自己最有利的方案;对处于MIN层的节点,选其子节点中一个最小的得分作为父节点的得分,这是为了立足于最坏的情况,这样计算出的父节点的得分为倒推值。

5.如此反推至根节点下的第一层孩子,如果其中某个孩子能获得较大的倒推值,则它就是当前棋局最好的走法。

把主要的算法写出来了,分为两个函数,一个为估值函数,即计算各个后续棋局(即叶子节点)的权值;另一个为遍历函数,生成指定遍历深度的博弈树。

 

伪代码:

function minimax(node, depth)
if node is a terminal node or depth = 0
    return the heuristic value of node
if the adversary is to play at node
    let α := +∞
    foreach child of node
        α := min(α, minimax(child, depth-1))
else {we are to play at node}
    let α := -∞
    foreach child of node
        α := max(α, minimax(child, depth-1))
return α。

 

模板(转自:)

站在MAX角度的评估函数:

static   final   int   INFINITY = 100 ;   // 表示无穷的值
static   final   int   WIN = +INFINITY ;   // MAX的最大利益为正无穷
static   final   int   LOSE = -INFINITY ;   // MAX的最小得益(即MIN的最大得益)为负无穷
static   final   int   DOUBLE_LINK = INFINITY / 2 ;   // 如果同一行、列或对角上连续有两个,赛点
static   final   int   INPROGRESS = 1 ;   // 仍可继续下(没有胜出或和局)
static   final   int   DRAW = 0 ;   // 和局
static   final   int [][] WIN_STATUS =   {
      {   0, 1, 2 },
      { 3, 4, 5 },
      { 6, 7, 8 },
      { 0, 3, 6 },
      { 1, 4, 7 },
      { 2, 5, 8 },
      { 0, 4, 8 },
      { 2, 4, 6   }
};
/**
 * 估值函数,提供一个启发式的值,决定了游戏AI的高低
 */
public   int   gameState( char []   board )   {
       int   result = INPROGRESS;
       boolean   isFull =   true ;
      
       // is game over?
       for   ( int   pos = 0; pos < 9; pos++) {
             char   chess = board[pos];
             if   ( empty   == chess) {
                  isFull =   false ;
                   break ;
            }
      }
       // is Max win/lose?
       for   ( int [] status : WIN_STATUS) {
             char   chess = board[status[0]];
             if   (chess ==   empty ) {
                   break ;
            }
             int   i = 1;
             for   (; i < status.length; i++) {
                   if   (board[status[i]] != chess) {
                         break ;
                  }
            }
             if   (i == status.length) {
                  result = chess ==   x   ? WIN : LOSE;
                   break ;
            }
      }
       if   (result != WIN & result != LOSE) {
             if   (isFull) {
                   // is draw
                  result = DRAW;
            }   else   {
                   // check double link
                   // finds[0]->'x', finds[1]->'o'
                   int [] finds =   new   int [2];
                   for   ( int [] status : WIN_STATUS) {
                         char   chess =   empty ;
                         boolean   hasEmpty =   false ;
                         int   count = 0;
                         for   ( int   i = 0; i < status.length; i++) {
                               if   (board[status[i]] ==   empty ) {
                                    hasEmpty =   true ;
                              }   else   {
                                     if   (chess ==   empty ) {
                                          chess = board[status[i]];
                                    }
                                     if   (board[status[i]] == chess) {
                                          count++;
                                    }
                              }
                        }
                         if   (hasEmpty && count > 1) {
                               if   (chess ==   x ) {
                                    finds[0]++;
                              }   else   {
                                    finds[1]++;
                              }
                        }
                  }
                   // check if two in one line
                   if   (finds[1] > 0) {
                        result = -DOUBLE_LINK;
                  }   else   if   (finds[0] > 0) {
                        result = DOUBLE_LINK;
                  }
            }
      }
       return   result;
}

 

限定层数的实现:

/**
 * 以'x'的角度来考虑的极小极大算法
 */
public   int   minimax( char [] board,   int   depth){
       int [] bestMoves =   new   int [9];
       int   index = 0;
      
       int   bestValue = - INFINITY ;
       for ( int   pos=0; pos<9; pos++){
            
             if (board[pos]== empty ){
                  board[pos] =   x ;
                  
                   int   value = min(board, depth);
                   if (value>bestValue){
                        bestValue = value;
                        index = 0;
                        bestMoves[index] = pos;
                  } else
                   if (value==bestValue){
                        index++;
                        bestMoves[index] = pos;
                  }
                  
                  board[pos] =   empty ;
            }
            
      }
      
       if (index>1){
            index = ( new   Random (System. currentTimeMillis ()).nextInt()>>>1)%index;
      }
       return   bestMoves[index];
      
}
/**
 * 对于'x',估值越大对其越有利
 */
public   int   max( char [] board,   int   depth){
      
       int   evalValue =   gameState (board);
      
       boolean   isGameOver = (evalValue== WIN   || evalValue== LOSE   || evalValue== DRAW );
       if (depth==0 || isGameOver){
             return   evalValue;
      }
      
       int   bestValue = - INFINITY ;
       for ( int   pos=0; pos<9; pos++){
            
             if (board[pos]== empty ){
                   // try
                  board[pos] =   x ;
                  
                   //   maximixing
                  bestValue = Math. max (bestValue, min(board, depth-1));
                  
                   // reset
                  board[pos] =   empty ;
            }
            
      }
      
       return   evalValue;
      
}
/**
 * 对于'o',估值越小对其越有利
 */
public   int   min( char [] board,   int   depth){
      
       int   evalValue =   gameState (board);
      
       boolean   isGameOver = (evalValue== WIN   || evalValue== LOSE   || evalValue== DRAW );
       if (depth==0 || isGameOver){
             return   evalValue;
      }
      
       int   bestValue = + INFINITY ;
       for ( int   pos=0; pos<9; pos++){
            
             if (board[pos]== empty ){
                   // try
                  board[pos] =   o ;
                  
                   //   minimixing
                  bestValue = Math.min(bestValue, max(board, depth-1));
                  
                   // reset
                  board[pos] =   empty ;
            }
            
      }
      
       return   evalValue;
      
}

这里对极小极大算法的实现只是其中一种可行性,实际上可能会看到很多种不同的实现方式,但道理是一样的。