AlphaBeta剪枝算法


關於AlphaBeta剪枝的文章太多,這個方法是所有其它搜索方法的基礎,得多花些時間認真地理解。

先把基本概念再回顧一遍:

節點:在中國象棋中就是一個棋盤的當前局面Board,當然該輪到誰走棋也是確定的。這里的圓形節點表示終止節點,在中國象棋里就是一方被將死的情況(或者到達了搜索的最大深度),后續不會再有着法產生,游戲如果走到這里就會結束。在引擎里通常給紅方一個很大的評估值,如+30000,給黑方一個很小的評估值,如-30000,來方便地判斷這種結束局面。(勝利局面有稍微不同的表示法,用-30000+層數ply來表示)

連線:表示一步着法Move,通過這步着法后,局面發生變化,先后手也要交換。

:通常的術語是ply,復數形式是plies,也有稱為levels,當然與depth也是對應的。這個術語是為了與比賽里常說的回合相區分,一個回合通常包含2步,這個ply就表示某一方走了一步。根節點記為0層,以下的層數遞增。

深度depth:要注意是從下到上的,還是從上到下的。(1)通常的算法書中都是從下到上遞增的,即根節點為最大搜索深度,走到最底部的葉子結點為0,這種算法只要記住一個depth值就可以了。(2)而另一種記法是根部結點為0,越向下depth增加,這時在搜索時就要傳遞2個參數值,depth和maxDepth,稍微有點啰嗦,應該也會影響一點效率。另外在探查置換表中的結點時,用第(1)種記法也方便一些,因為要知道從當前節點迭代的深度值,否則還要在置換表中保存depth和maxDepth兩個值。

 

AlphaBeta剪枝方法是對Minimax方法的優化,它們產生的結果是完全相同的,只不過運行效率不一樣。

這種方法的前提假設與Minimax也是一樣的:

1)雙方都按自己認為的最佳着法行棋。

2)對給定的盤面用一個分值來評估,這個評估值永遠是從一方(搜索程序)來評價的,紅方有利時給一個正數,黑方有利時給一個負數。(如果紅方有利時返回正數,當輪到黑方走棋時,評估值又轉換到黑方的觀點,如果認為黑方有利,也返回正數,這種評估方法都不適合於常規的算法描述)

3)從我們的搜索程序(通常把它稱為Max)看來,分值大的數表示對己方有利,而對於對方Min來說,它會選擇分值小的着法。

但要注意:用Negamax風格來描述的AlphaBeta中的評估函數,對輪到誰走棋是敏感的。

也就是說:

在Minimax風格的AlphaBeta算法中,輪紅方走棋時,評估值為100,輪黑方走棋評估值仍是100。

但在Negamax風格的AlphaBeta算法中,輪紅方走棋時,評估值為100,輪黑方走棋時評估值要為-100

 

alphabeta

貼一段偽代碼:

 
def ABNegaMax (board, depth, maxDepth, alpha, beta)
if ( board.isGameOver() or depth == maxDepth )
    return board.evaluate(), null
bestMove = null
bestScore = -INFINITY
for move in board.getMoves()
    newBoard = board.makeMove(move)
    score = ABNegaMax(newBoard, maxDepth, depth+1, -beta, -max(alpha, bestScore))
    score = -score
    if ( score > bestScore )
        bestScore = score
        bestMove = move
        # early loop exit (pruning)
        if ( bestScore >= beta ) return bestScore, bestMove
return bestScore, bestMove


用下列語句開始調用:

ABNegaMax(board, player, maxDepth, 0, -INFINITY, INFINITY)


// method call with depth 5 and minimum and maximum boundaries
// minimaxValue = alphaBeta(board, 5, -MATE, +MATE)
int alphaBeta(ChessBoard board, int depth, int alpha, int beta)
{
    int value;
    if( depth == 0 || board.isEnded())
    {
         value = evaluate(board);
         return value;
    }
    board.getOrderedMoves();
    int best = -MATE-1;
    int move; 
ChessBoard nextBoard;
while (board.hasMoreMoves()) { move = board.getNextMove(); nextBoard = board.makeMove(move); value = -alphaBeta(nextBoard, depth-1,-beta,-alpha); if(value > best) best = value; if(best > alpha) alpha = best; if(best >= beta) break; } return best; }

下面這個PDF更清楚地說明了Negamax風格的alphabeta算法的過程:

http://www.cs.colostate.edu/~anderson/cs440/index.html/lib/exe/fetch.php?media=notes:negamax2.pdf

 

 

為了更准確地理解minmax和negamax兩種不同風格的搜索執行過程,畫出了詳細的圖解,發現negamax更容易理解了,代碼也確實精練了不少。

當采用了置換表算法后,還需要對PV-Nodes, Cut-Nodes和All-Nodes三種類型結點的含義以及如何變化有更准確地了解。

alphabeta搜索的詳細過程(min,max風格)

 

alphabeta搜索的詳細過程(negamax風格)

可以發現,Negamax風格的算法有下面的特點(代碼來自象棋巫師):

        int AlphaBeta(int depth, int alpha, int beta) {
              int hashf = hashfALPHA;    //開始時的結點類型應該是All-Nodes,有些地方稱為ALPHA類型結點
              //這里要探查置換表
              if (depth == 0) {   // 葉子結點,評估,寫入置換表,返回即可
                 val = Evaluate();
                 RecordHash(depth, val, hashfEXACT); // 葉子結點肯定是PV-Nodes
                 return val;
              }
              GenerateLegalMoves();
              while (MovesLeft()) {
                 MakeNextMove();
                       //注意Negamax風格的調用方式,前面有個負號,后面的參數是-beta和-alpha

               // Negamax的含義中Nega就是指這里的負號
                 val = -AlphaBeta(depth - 1, -beta, -alpha);  
                 UnmakeMove();
                 if (val >= beta) {   // 剪枝情況判斷
                    RecordHash(depth, beta, hashfBETA);  //這時的結點類型是Cut-Nodes
                    return beta;
                 }
                 if (val > alpha) {   // Negamax中的max就是指的這一段,要記錄最大的評估值,這里沒有引入一個新變量,直接就用了alpha變量
                    hashf = hashfEXACT;   // 只要alpha值一發生變化,這個結點類型就是PV-Nodes了!
                    alpha = val;
                 }
               }
               RecordHash(depth, alpha, hashf);
               return alpha;   // 此時的alpha就是記錄了當前結點的所有子結點的最大的負評估值!
           }

 

這面的代碼在置換表探查方面感覺有點問題,又從Marek Strejczek的論文《Some aspects of chess programming》的第87頁上找到一段代碼,感覺這段代碼充分利用了置換表中存儲的信息。

chSCORE alphabetaWithTT(chPOSITION node,chSCORE alpha,beta) {
if (isLeaf(node) ) return evaluate(node);
    if ( (entry = getFromHash(node) ) != NULL) {
        if (TT_entry_deep_enough) {
        // data in hash table comes from a search to the 
        // same depth as current or deeper – so it is reliable
        if (entry.flag == UPPER) {
            if (entry.score <= alpha) {
                return alpha
            }
            if (entry.score < beta)
                beta = flag.score;
            }
        }
        if (entry.flag == LOWER) {
             if (entry.score >= beta) {
                 return beta;
             }
             if (entry.score > alpha) {
                 alpha = flag.score;
             }
         }
         if (entry.flag == EXACT) {
             return entry.score
         }
    }
    else {
        // TT entry represents results of a shallower
        // depth search – no cutoffs possible, but still 
        // a valuable move to try first can be used
        if (entry.move != NULL) {
             try_hash_move_first = true;
        }
    }
}
g = alpha;
x = left_most_child(node);
hash_flag = UPPER;
best_move = NULL;
while (isNotNull (x) ) {
    g = -alphabeta(x, -beta, -alpha);
    if (g > alpha) {
        alpha = g;
        if (g >= beta) {
            hash_flag = LOWER;
            best_move = current_move;
            saveHash(node,g,hash_flag,best_move);
            return beta;
        }
        hash_flag = EXACT;
        best_move = current_move;
    }
    x = next_brother(x);
}
putToHash(node, g, hash_flag, best_move)
return alpha;
} // end of alphabetaWithTT

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM