極小極大搜索的算法過程:
參考文檔:http://www.xqbase.com/computer/search_minimax.htm (經典)
主要思想比較簡單,但說清楚也不大容易。其核心思想是通過對於以后的狀態進行預見式的暴搜,對可能的狀態進行分析。理論上,如果能夠搜索到最終狀態,那么之后的走法都已經是確定的了。(這個地方覺得有些糊塗)
對於局面形式的估計首先需要一個合理的估價函數。實際上是因為真正的搜索幾乎都是無法搜索到所有的可能性,否則完全用0和1就能表示當前局面的勝負態了。所以需要對局面進行較為合理的分析估價。對於某一方來說都是要使得最終局面狀態值(理論上,最終局面狀態有且僅有一個)獲得最大,所以對於兩方來說,可以通過一方估價正越大表示勝率越大,另一方估價正越小(負越大)表示勝率越大。
因此出現了極小極大搜索算法。
從最簡單的情況開始分析,首先確定我方要使得最終局面估價最大,而非當前局面估價最大,所以需要預測下一個我方局面的估價如何。而在此之前的一步掌握權在對方的手上,自然會選擇對他有利的狀態(一定是最終局面形式最大的狀態走法),也就是走對於對方最終局面估價最大的狀態。因此我方的落子是根據所有下一個狀態對方會如何走來決定的。最終局面估價是雙方共同決定的。
(常常會被Min和Max狀態搞亂,其實不要管這個,花時間弄明白其中的含義,寫出來自然能夠明白(其實Min()表示走到當前可預見的最后的狀態的最小值,Max()反之))
偽代碼如下:
int MinMax(int depth) { if (SideToMove() == WHITE) { // 白方是“最大”者 return Max(depth); } else { // 黑方是“最小”者 return Min(depth); } } int Max(int depth) { int best = -INFINITY; if (depth <= 0) { return Evaluate(); } GenerateLegalMoves(); while (MovesLeft()) { MakeNextMove(); val = Min(depth - 1); UnmakeMove(); if (val > best) { best = val; } } return best; } int Min(int depth) { int best = INFINITY; // 注意這里不同於“最大”算法 if (depth <= 0) { return Evaluate(); } GenerateLegalMoves(); while (MovesLeft()) { MakeNextMove(); val = Max(depth - 1); UnmakeMove(); if (val < best) { // 注意這里不同於“最大”算法 best = val; } } return best; }
一個簡便的實現方法,通過來回正負的變化來減少代碼量,便於維護。
int NegaMax(int depth) { int best = -INFINITY; if (depth <= 0) { return Evaluate(); } GenerateLegalMoves(); while (MovesLeft()) { MakeNextMove(); val = -NegaMax(depth - 1); // 注意這里有個負號。 UnmakeMove(); if (val > best) { best = val; } } return best; }
剪枝方法也有很多,最經典的莫過於alpha-beta剪枝了。
參考文檔:http://www.xqbase.com/computer/search_alphabeta.htm
1)β剪枝:
說的通俗一些,比如當前是我方下子,並且下一個我方局面的估價已經完成(遞歸),即博弈樹的第三層已經預知。中間第二層即對方局面,可知對方走的必然是使得最終局面估價最小的一步,故我方當前要下子顯然要使得在對方走估價最小的一步能達到的最大的估價。也就是第一層選取的走法是走向 第二層每一個局面對應的最小最終估價走法到達的 最大的局面。
實現中,你實際上不是站在第一層的視角來看,而是在第二層搜索時進行的,故需要保留第一層已經搜索的最大值,而對於第二層的對手來說,我們是敵人。從上帝視角來看,alpha值為當前棋手預估的最終估價的最大值,beta值為上一個局面(實際上先前那個局面還沒下,保留對於先前那個局面)對手棋手預估的最終估價的最小值。也就是如果當前走法能夠走到比先前那個局面的棋手預估的最終估價的最小值要大(對手顯然不會走這步,因為至少估價比當前小),就直接返回(因為對手不會讓你走到這個狀態,所以之后怎么走都不用管了)。
2) α剪枝:即相反的情況。
偽代碼如下:
int AlphaBeta(int depth, int alpha, int beta) { if (depth == 0) { return Evaluate(); } GenerateLegalMoves(); while (MovesLeft()) { MakeNextMove(); val = -AlphaBeta(depth - 1, -beta, -alpha); UnmakeMove(); if (val >= beta) { return beta; } if (val > alpha) { alpha = val; } } return alpha; }
