在上一篇咱們分析了對抗搜索的基本思想,本文在上一篇的基礎上對算法部分步驟進行優化。首先咱們看一個基於對抗搜索創建的博弈樹,對上篇的內容作一個簡單的回顧。(本篇內容可使用上篇開頭說的兩本書做爲參考)java
觀察這顆博弈樹,咱們可知它的固定深度是3,MAX先執行。換句話來講就是,MAX爲了決定第一步該走A、B仍是C,它要從根節點開始遍歷深度爲3的子樹。再換句話來講,這顆樹其實只是爲了得到MAX在第一步走的時候創建的博弈樹;一樣對於MIN方來講,假如MAX第一步選擇走A,那麼以A節點爲根節點,一樣創建一顆深度爲3的子樹,經過遍歷該子樹,得到MIN在這一步的最佳選擇,A一、A2或者A3(未畫出該圖)。算法
通俗的說,對抗搜索其實就是在有限深度內枚舉雙發在每一步的選擇,經過比較最終節點的狀態(最大深度時)優劣,來得到此時執行方的最優選擇。函數
迴歸到原圖,對MAX的選擇進行一個詳細的分析(請結合上一篇的MAX_VALUE和MIN_VALUE代碼):優化
接下來咱們思考一個問題,咱們能夠看到上述的算法是對全部的候選項都進行枚舉遍歷比較,這樣若是深度比較大時,好比固定截取深度爲10,那麼對每一步來講都要計算最大深度爲10的子樹,這樣的計算量會很是大。有沒有可能縮小比較範圍?spa
首先咱們要清楚,max_value和min_value方法以DFS(深度優先)進行遍歷。接下來咱們對以A節點爲根節點的子樹進行詳細分析。首先從深度3開始往上回溯時,此時A1的最優選擇爲6;此時再往上回溯,A節點此時的最優選擇爲6;而後開始遍歷A2節點,由於A節點此時的值是6,若是A2節點可以獲得該值的信息的話,那麼在以A2節點爲根的深度遍歷時,遇到比6大的值,其實能夠放棄該子樹的後續遍歷了。緣由就是A2節點是MAX方來執行,若是A2的子節點有比6大的,那麼MAX方確定會選擇比6大的值,而A節點是MIN方來執行,它要選擇的是A一、A二、A3的最小值,因此此時A節點確定不會選擇A2做爲最優選擇。code
一樣,在A的選擇結束後,向上回溯到R,此時R的最優選擇是6,因爲R要選取最大值,那麼在對剩餘以B和C爲根節點進行深度遍歷時,若是有遇到比6小的值,那麼該節點確定不是最優的選擇。緣由就是MAX方要選擇後繼節點中的最大值,比6小的話,確定不是最優選擇。blog
上述方法就是樹結構最經常使用的優化方法--剪枝,這裏的優化方法叫作Alpha-Beta方法。下面是經過Alpha-Beta剪枝方法對max_value方法和min_value方法的改進。遞歸
int max_value ( int dep , state s , int alpha , int beta ){ if ( terminal ( s )) return e ( s ); //終止狀態 if ( dep == maxdepth ) return e ( s ); //深度截斷,返回評價函數 v = - inf ; //初始化爲負無窮 succ = make_successors ( s ); // succ [ i爲第]個後繼狀態i for ( i = 0; i < succ . count ; i ++){ v = max (v , min_value ( succ [ i ] , alpha , beta )); //計算全部兒子的最大值 if ( v >= beta ) return v ; //β剪枝 alpha = max ( alpha , v ); //更新α爲最大值 } return v ; }
int min_value ( int dep , state s, int alpha, int beta){ if ( terminal ( s )) return e ( s ); //終止狀態 if ( dep == maxdepth ) return e ( s ); //深度截斷,返回評價函數 v = inf ; //初始化爲無窮大 succ = make_successors ( s ); // succ [ i爲第]個後繼狀態i for ( i = 0; i < succ . count ; i ++){ v = min (v , max_value ( succ [ i ] , alpha , beta )); //計算全部兒子的最小值 if ( v <= alpha ) return v ; //α剪枝 beta = min ( beta , v ); //更新β爲最小值 } return v ; }
接下來咱們按照改進後的max_value方法和min_value方法對上述的博弈樹進行剪枝,方便你們理解。terminal
最終的裁剪圖以下,標紅的表明它的子樹都被裁剪了。class
alpha表明的是MAX方選擇的最小下界;
beta表明的是MIN方選擇的最大上界;