
我們很容易明白,在alpha-beta剪枝算法中,會遇到重複的棋局。如下面的兩個圖的走法是一樣的,不必要将兩個局面的得分都計算一次。
是以需要将棋盤的資訊儲存起來以備後續使用,這就是置換表。在棋類遊戲中經常使用的就是zobrist哈希。
zobrist哈希并不是一種最好的雜湊演算法,但是确實最高效的。有時候我們必須在最好與效率之間做出選擇。
zobrist是一個人的名字,源自他1970的年的一片論文
試想一下你如何有效的存儲一個棋盤的資訊呢?zobrist提供了一種辦法:棋盤的一個唯一的辨別就是棋盤的所有狀态的異或值。
棋盤的每一個狀态實際就對應着一個随機數。在五子棋中,我們忽視空白,将每一個位置的狀态标示為黑棋或白棋兩種狀态。
第一步:是需要初始化每一個狀态的随機數。設定兩個數組。對于随機數的産生使用了Twister算法,開源項目:www.agner.org/random
其實就是用别人寫好的,調用一下函數,不要想得那麼複雜。rand函數隻能生成五位随機數,不推薦使用。
int WriteZobrist[15][15];
int BlackZobrist[15][15];
void Game::InitZobrist()
{
int seed = (int)time(0); // random seed
// choose one of the random number generators:
CRandomMersenne RanGen(seed); // make instance of random number generator
for(int i= 0;i<15;i++)
{
for(int j = 0;j<15;j++)
{
WriteZobrist[i][j]=RanGen.IRandom(0,INT_MAX);
BlackZobrist[i][j]=RanGen.IRandom(0,INT_MAX);
}
}
}
接下來對于這個棋局的哈希值,是多少呢?
黑棋在(6,6),(8,8)點
白棋在(6,8),(8,6)點
hashcode =WriteZobrist[6][8]^WriteZobrist[8][6]^BlackZobrist[6][6]^BlackZobrist[8][8]
就這麼簡單啊兄弟。由于異或的特性,是以,在五子棋中,下一個子後,隻需要用hashcode異或一次就可以啦。
接下來就是考慮一下hash沖突,由于hash沖突問題常常需要加上一個64位的校驗位。來保證這個棋局是哈希表中的棋局,減少沖突。
longlong WZobrist[15][15];
longlong BZobrist[15][15];
第二、一定要注意,棋局轉到hashcode隻是第一步,第二步是要把hashcode轉換為位址存到hash表中。這兩步都可能會發生沖突。把hashcode轉換為位址存到hash表中簡單的辦法就是除表的大小取餘數。是以,哈希表越大沖突就越小。
第三、一定要存儲深度,我們來看一段zobrist在1970年的描述:
這段話就說明了深度是重要的,因為在博弈算法中,更深的分數意味着更穩健,是以,在第3層的局面可以作為di5層局面的參考,因為第3層搜尋得更深。這段話同時說明了在剪枝中如何使用。
第四、在剪枝中還需要考慮一些額外的極大值極小值問題。着實有點繁瑣。是以必須要存儲極大值極小值資訊。
一段僞代碼:
最後要說的是,置換表用在五子棋上沒多大的效率提升,一般是用在象棋等遊戲上。