本質上和Alpha-Beta算法一樣,但不以"極大-極小搜索算法"爲出發點。與樹型圖的結合更加緊密。
一. 搏弈樹
紅棋走一步後,黑棋有多種應對招法。黑棋走完後,紅棋又有多種走法可選。依次類推,就構成了一個搏弈樹。
【 圖1 】
二. 靜態評分
如圖1所示,在某個點上,不考慮後續步法,僅就雙方當前形勢進行評分。評分以棋子質量爲基礎。如果該點是紅方走的棋,得分爲紅方質量爲r減去黑方質量爲b (r-b)。如果該點是黑方走的棋,分值爲b-r。
(除質量外,還可以考慮棋子的攻擊力、防禦力、敏捷性等因素。我使用的是JS語言,只考慮棋子質量,這樣性價比高)
三. 動態評分
受計算機性能限制,搏弈樹的層次是有限的。如果不考慮水平線效應【注1】,在葉子結點,靜態分值就是其最終得分。在非葉子節點,父結點分值等於最大子節點分值的相反數。比如某點是紅方棋,靜態分值爲2。接下來黑棋有4種應對走法,靜態分值依次是-1、0、1,3。不難理解,與前面的2分相比,後面的-max(-1,0,1,3) = -3 更準確反映出前一步紅棋的優劣。同樣道理,黑棋的4個分值也可再遞歸下去,層數越多,結果越準確。
四. 剪枝原理
如圖1所示,已知G點得分爲-9,K點得分爲10。根據-max方法,H點得分將小於等於-10,H肯定不如G好,那麼L點及其後續 節點就沒必要再搜索了。left-upperleft搜索就是基於這麼簡單的原理。
五. 僞碼示例
/*
* depth:當前深度
* upperLeft:當前節點"大爺"的分值
* left:當前節點"哥哥"的分值
*/
function dynamism(depth,upperLeft,left)
{
if(depth >= MAXDEPTH){
return quiescence();//靜態評分函數
}
else{
var arr = getMoves();//獲取行棋方着法
var val = upperLeft;
for(var i=0;i<arr.length;++i){
var v = dynamism(depth+1,left,val);
if(v >= -left){ // 剪枝!
return -v;
}
if(v > val){
val = v;
}
}
return -val;
}
}
假設我們要用該方法獲取圖1中H點動態評分,那麼G點得分作爲left值,B點得分作爲upperLeft值。
參數left的意義:
當前節點得分不能低於left值(如果哥哥更好,自己就沒存在必要了)。遍歷當前點的子節點,它們得分不能高於-left,否則觸發剪枝。
參數upperLeft的意義:
當前點得分不能大於-upperLeft(否則可證明父節點是個失敗着法,自己也就必要繼續存在了)。爲了不讓自己的祖輩失敗,子節點不能低於某個分值。比如搜索H點時,其upperLeft爲8(B點得分)。如果H點得分大於-8則C點失敗。爲了不讓C點失敗,K或者L的值大於8纔是有效的。
六. 初始參數
dynamism(0,-9999,-9999);
因爲當前點的值不小於left,所以left初始值賦以負極大值。因爲當前點的值不大於-upperLeft,所以upperleft初始賦負極大值。
七. 與alpha-beta算法的關係
beta相當於-left,alpha相當於upperLeft。alpha-beta算法內循環過程中分值取反,但最終返回值不取反。另外depth採用遞減方式。
注1:水平線效應
搏弈樹上,葉子結點採用靜態評分,非葉子結點採用動態評分。在葉子結點,如果某步棋喫掉了對方一子,那麼走棋方會在質量上暫時取得較大優勢。但下一步對方很可能再喫回來甚至取得更大優勢。如果交替喫下去雙方分值將反覆震盪,稱爲水平線效應。水平線效應是靜態評分函數要克服的最大困難。爲了克服水平線效應,靜態評分函數實際上也是遞歸調用的。
參考文章: