• 沒有找到結果。

Alpha-Beta 搜尋演算法

在文檔中 電腦暗棋之設計及實作 (頁 26-34)

第三章 搜尋演算法

第五節 Alpha-Beta 搜尋演算法

第五節 Alpha-Beta 搜尋演算法

在Min-Max 或是 Nega-Max 搜尋演算法中,對於對局樹裡的每個節點的每一

個所有可能著法都要走過一次,假設對局樹中每個節點平均有 30 個著法,如果

要搜尋N 層,就要拜訪大約 30N個節點,因此這樣做需要耗費相當多的時間,效

率非常地差[7]。

Brudno 在 1963 年提出了 Alpha-Beta 搜尋演算法,它在大部份的情況下會比

Min-Max 有更佳的搜尋效率。而 Alpha-Beta 的核心思想是:在搜尋的過程中,發

現無論如何都無法改變對方目前的最佳分數時,就可以提早放棄,不必浪費時間

搜尋其它的著法。例如圖3-6:

圖3-6 Alpha-Beta 示意圖

圖 3-6 中,方形節點會從子節點中取最大值,圓形節點會從子節點中取最小值,

而葉節點則會傳回以根節點為評分角度的審局函數值。其中節點A,經由深度優

先搜尋左子樹,得到左子節點的值為30,由於節點 A 會從其子節點中取最大值,

所以A 的值必不小於 30,換句話說,A 的右節點的值必須大於 30 ,才可能會被

A 選上。

當找尋過B 的左子節點時,得到分數 20,因為 B 要取最小值,所以 20 便是

B 的最大可能值。然而,B 之值必須大於 30 才會被 A 選上,且 B 剩下的子節點

無論分數如何,都不可能使B 的值大於 30,所以就不用往下搜尋剩下的節點了。

Alpha-Beta 搜尋演算法必須使用兩個參數,分別是 α 和 β。其中 α 是記錄最

大層節點目前的最大值,而β 是記錄最小層節點的最小值,兩個參數以傳值 call

by value 的方式傳遞給下層的子樹。如果在取最大值的時候,發現了一個大於等

於β 的值,就不用再對其它分枝進行搜尋,這就是所謂的 β 截斷;同理,在取最

小值的時候,發現了一個小於等於α 的值,也不用再對其它分枝進行搜尋,這就

是所謂的α 截斷。例如圖 3-7 是 Alpha-Beta 另一個比較複雜的範例:

圖3-7 Alpha-Beta 示意圖

圖3-7 是 Alpha-Beta 以深度優先從左至右拜訪的樹狀圖,其節點截斷順序如下:

1. B 節點取值 25 的時候,25 ≧ 20,造成 C 節點的截斷(β 截斷)。

2. E 節點取值 6 的時候,6 ≦ 20,造成 F 節點的截斷(α 截斷)。

3. H 節點取值 3 的時候,3 ≦ 6,造成 I 節點的截斷(α 截斷)。

4. J 節點取值 6 的時候,6 ≦ 20,造成 K 節點的截斷(α 截斷)。

很顯然地,Alpha-Beta 搜尋演算法的搜尋效率與走法的排列順序有極密切的

關係。如果總是先嘗試壞的走法的話,那最終將會與 Min-Max 搜尋演算法一樣,

完全沒有任何截斷的機會。倘若能將好的走法排在前面優先嘗試的話,就可以儘

早發生截斷,省掉對其它節點不必要的搜尋。例如圖3-7 中,總共有 31 個節點,

最終只搜尋了19 個節點,省了 12 個節點。在最佳走法搜尋順序時,Knuth 和 Moore

int AlphaBeta(int depth,int alpha,int beta){

int val;

if(depth==0){

return Evaluate();

}

GenerateLegalMoves();

while(MovesLeft()){

MakeNextMove();

val= -AlphaBeta(depth-1,-beta,-alpha); //alpha 和 beta 順序對調 //且加上負值

UnMakeMove();

if(val>=beta){

return beta; //beta 截斷 }

if(val>alpha){

alpha=val;

} }

return alpha;

}

以Nega-Max 形式寫成的 Alpha-Beta 搜尋演算法,當 α 和 β 兩個參數在傳遞

給下層的時候,是將兩個順序對調,並加上負值,這樣做使得α 截斷也能簡化成

以β 截斷的形式表現出來,消除了 Min-Max 形式的 Alpha-Beta 搜尋演算法,在 α

截斷與β 截斷判斷形式上的不同,並且讓程式碼更簡潔有力。在我們這次暗棋程

式的實作中,也是以Nega-Max 形式的 Alpha-Beta 搜尋演算法為其最主要核心。

第六節 寧靜搜尋

一般利用Alpha-Beta 搜尋演算法所展開的對局樹深度有限,所以在某一固定

深度之下的情形將會被忽略,這時就經常會有所謂的水平效應(Horizontal Effect)

的產生。例如圖3-9 輪黑方走搜尋 10 層:

圖3-9 水平效應示意圖

當搜尋路徑為:C1-D1,F2-F4 吃卒,D1-E1 吃炮,H3-H4 吃卒,E1-D1,H4-H3,

D1-C1,E4-E3,C3-C4,F4-C4 吃馬,總共 10 步後,到達如圖 3-10 的盤面:

圖3-10 水平效應示意圖

此時因為沒有剩餘深度可以讓黑方嘗試走B4-C4 吃炮,導致黑方最後得到吃炮+

18 分、損失兩隻卒-2 分、損失一隻馬-6 分,總共 10 分這樣誤判的結果。

為了解決上述的問題,我們使用寧靜搜尋(Quiescent Search)。當沒有剩餘

深度時不立刻返回審局函數的評估分數值,而是根據盤面只針對吃子步進行展開

,直到最後的寧靜盤面,也就是不再有棋子被吃為止,才返回審局函數的值。這

樣的做法多少可以克服因水平效應而造成的搜尋不準確的結果。

圖3-11 是 Quiescent Search 的虛擬碼:

圖3-11 Quiescent Search 虛擬碼

在我們撰寫 Quiescent Search 的過程中,有看過許多版本的虛擬碼,它們都

會在GenerateCapMoves()之前,先呼叫審局函數 Evaluate(),看審局函數的值是否

可以造成β 截斷,如果可以造成 β 截斷就直接返回 beta 值,不過我們發現這樣做

的結果可能會導致搜尋結果不正確。原因如圖 3-12,輪黑方走,進行 10 層的搜

尋:

int Quies(int alpha,int beta){

int val;

GenerateCapMoves(); //只產生吃子步 while(MovesLeft()){

MakeNextMove();

val= -Quies(-beta,-alpha); //遞迴呼叫時不再需要 depth 參數 UnMakeMove();

if(val>=beta){

return beta;

}

if(val>alpha){

alpha=val;

} }

return alpha;

}

圖3-12 寧靜搜尋示意圖

當搜尋路徑為:F1-G1,F2-G2,H3-H2,E1-F1,H2-G2 吃兵,F1-E1,B1-A1,

E1-F1,A1-B1,F1-G1 吃包,總共 10 步後,到達如圖 3-13 的盤面:

圖3-13 寧靜搜尋示意圖

此時輪黑方走,如果這時呼叫審局函數Evaluate(),會得到-17 分的值,而這時窗

口的beta 值是-18,-17 大於-18 會造成所謂的 β 截斷,所以將分數回傳上去,最

後導致圖3-12 輪黑方走的搜尋結果為-17 分。這樣的搜尋結果是錯的,因為正確

的結果應該是紅、黑雙方在10 層的搜尋內,互相都吃不到對方的子,返回 0 分

才對。所以,寧靜搜尋當盤面還有吃子步可以嘗試的時候,不應該直接用審局函

數的值來當做是否可以截斷的依據,而直接返回beta 值,以免造成搜尋結果的不

正確。

在文檔中 電腦暗棋之設計及實作 (頁 26-34)

相關文件