既然Alpha-Beta搜索算法是在「最大-最小」的基礎上引入「樹的裁剪」的思想以期提升效率,那麼它的效率就取決於樹的結構——若是搜索了沒多久就發現能夠「裁剪」了,那麼須要分析的工做量將大大減小,效率天然也就大大提升;而若是直到分析了全部的可能性以後才能做出「裁剪」判斷,那此時「裁剪」也已經失去它原有的價值(由於你已經分析了全部狀況)。於是,要想保證Alpha-Beta搜索算法的效率就須要調整樹的結構,即調整待搜索的結點的順序,保證「裁剪」能夠儘量早地發生。算法
咱們能夠根據部分已經搜索的結果來調整將要搜索的結點的順序。由於一般一個局面經搜索被認爲較好時,在其後繼結點中每每有一些類似的局面(如某些可有可無的棋子位置有所不一樣)也是較好的。「歷史啓發」就是創建在這樣一種觀點之上的。在搜索的過程當中,每當找到一個好的走法,咱們就給該走法累加一個增量以記錄其「歷史得分」,一個屢次被搜索並認爲是好的走法的「歷史得分」就會較高。對於即將搜索的結點,按 「歷史得分」的高低對它們進行排序,保證較好的走法(「歷史得分」高的走法)排在前面,這樣Alpha-Beta搜索就能夠儘量早地進行「裁剪」,從而保證了搜索的效率。編程
下面分別是歷史啓發部分HistoryHeuristic.h以及着法排序部分SortMove.h的代碼實現,着法排序可使用各類排序算法,這裏我直接借用了王小春的《PC 遊戲編程(人機博弈)》中採用的「歸併排序」。數組
歷史啓發:數據結構
// HistoryHeuristic.h #include // For void *memset( void *dest, int c, size_t count ); /////////////////// Data Define /////////////////////////////////////////////// int HistoryTable[90][90]; //歷史記錄表 /////////////////// Function Prototype //////////////////////////////////////// // 清空歷史記錄表(全置0) inline void ResetHistoryTable(); // 取給定走法(move)的歷史得分,返回該得分 inline int GetHistoryScore( CCHESSMOVE *move ); // 爲一最佳走法(move)增添歷史記錄得分,nDepth標誌該走法所屬的搜索層數 inline void EnterHistoryScore( CCHESSMOVE *move, int nDepth ); ////////////////// Programmer-Defined Function //////////////////////////////// inline void ResetHistoryTable() { memset( HistoryTable, 0, sizeof(int)*8100 ); } inline int GetHistoryScore( CCHESSMOVE *move ) { int nFrom, nTo ; // 將位置座標轉換爲數組下標 nFrom = move->ptFrom.x * 9 + move->ptFrom.y; nTo = move->ptTo.x * 9 + move->ptTo.y; return HistoryTable[nFrom][nTo]; // 返回歷史紀錄表中的分數 } inline void EnterHistoryScore( CCHESSMOVE *move, int nDepth ) { int nFrom, nTo; // 將位置座標轉換爲數組下標 nFrom = move->ptFrom.x * 9 + move->ptFrom.y; nTo = move->ptTo.x * 9 + move->ptTo.y; HistoryTable[nFrom][nTo] += 2 << nDepth; // 增量爲2的nDepth次方 } // end of HistoryHeuristic.h
着法排序:函數
// SortMove.h /////////////////// Data Define /////////////////////////////////////////////// CCHESSMOVE cmTargetBuffer[80]; //排序用的緩衝隊列 /////////////////// Function Prototype //////////////////////////////////////// // 對長度爲nCount的着法隊列cmSource進行歸併排序 void MergeSort( CCHESSMOVE *cmSource, int nCount ); // 爲MergeSort函數所調用,歸併排序首尾相接的兩個數組, void MergePass( CCHESSMOVE *cmSource, CCHESSMOVE *cmTarget, int nLength, int nCount ); // 爲MergePass函數所調用,歸併排序首尾相接的兩個數組(這兩個數組已分別排好序) //!!!降序 !!! // 兩個數組爲cmSource[beginOne]--cmSource[endOne]; //cmSource[beginTwo]--cmSource[endTwo]. 其中beginTwo = endOne + 1 inline void Merge_Desc( CCHESSMOVE *cmSource, CCHESSMOVE *cmTarget, int beginOne ,int endOne, int endTwo ); ////////////////// Programmer-Defined Function //////////////////////////////// void MergeSort( CCHESSMOVE *cmSource, int nCount ) { int nLength = 1; // 藉助cmTargetBuffer對cmSource進行歸併排序 while( nLength < nCount ) { MergePass( cmSource , cmTargetBuffer , nLength , nCount ); nLength += nLength ; MergePass( cmTargetBuffer , cmSource , nLength , nCount ); nLength += nLength ; } } void MergePass( CCHESSMOVE *cmSource, CCHESSMOVE *cmTarget, int nLength, int nCount ) { int i = 0; // 表記待排序的第一個數組的起點 while( i + 2 * nLength <= nCount ) // 剩餘的元素個數大於等於兩個nLength長度 { // 歸併排序長度爲 nLength 的首尾相接的兩個數組 Merge_Desc( cmSource, cmTarget, i, i + nLength - 1, i + 2 * nLength - 1 ); i += 2 * nLength ; } if( i + nLength < nCount ) //剩餘的元素個數小於兩個nLength長度, { //但大於一個nLength長度 // 歸併排序長度爲 nLength 的數組和與其相接的剩下的全部元素組成的數組 Merge_Desc( cmSource, cmTarget, i, i + nLength - 1, nCount - 1 ); } else // 剩餘的元素個數小於等於一個nLength長度 { int j; // 接上剩下的已經排序好的數組 for ( j = i ; j < nCount ; j ++ ) cmTarget[j] = cmSource[j]; } } inline void Merge_Desc( CCHESSMOVE *cmSource, CCHESSMOVE *cmTarget, int beginOne ,int endOne, int endTwo ) { int i = beginOne , j = endOne + 1,// j = beginTwo k = beginOne ; while( i <= endOne && j <= endTwo ) //取兩個數組中值大的一個放入cmTarget { //(降序) if( cmSource[i].nScore >= cmSource[j].nScore ) cmTarget[k++] = cmSource[i++]; else cmTarget[k++] = cmSource[j++]; } if( i <= endOne ) // 第一個數組還沒有完,則接上第一個數組的剩餘部分 { int q; for( q = i; q <= endOne; q ++ ) cmTarget[k++] = cmSource[q]; } else // j <= endTwo 第二個數組還沒有完,則接上第二個數組的剩餘部分 { int q; for( q = j; q <= endTwo; q ++ ) cmTarget[k++] = cmSource[q]; } } // end of SortMove.h