前面講到了剪枝算法作爲一種對於極大極小博弈算法的優化,是提高效率的一種好辦法。但是我們不滿足於此。因爲一般的普通人是可以思考到6層以上。爲了對效率進行提高,我們還有兩種辦法。想象一下如下圖,電腦可能走棋如果從4個變爲2個,那麼效率的提升就會提高一倍。
其實深入下去就會發現,這些優化都是理所當然。因爲有一些明顯的不好的點直接就可以排除掉。就比如在最開始的時候,沒有人會在最左上的0,0下子一樣。在實際中,我進行的優化包括:
1、我只會在已有點不超過距離2的區域內下子。如下圖可能的下子範圍,直接縮小區域
bool Game::hasNeighbor(int x,int y) //距離爲2之類返回true
{
bool flag =false;
int redirect[8][2] = {{1,0},{1,1},{1,-1},{0,1},{0,-1},{-1,0},{-1,1},{-1,-1}};
for(int j = 1;j<=2;j++)
{
for(int i = 0;i<8;i++)
{
int m = j*redirect[i][0]+x;
int n = j*redirect[i][1]+y;
if(m>=0 && m<15 && n >=0 && n<15 && chess[m][n])
{
flag = true;
break;
}
}
}
return flag;
}
2、啓發式搜索:
在實際中,你可以使用一些手段來判斷一些點明顯的好壞。還記得之前講的策略表,我使用的策略表就有判斷一個點好壞的公式。如果一個優秀的策略表,最優選擇點有超大概率會是排名在前10的這些點鐘。所以我直接就可以將每一步可能的節點都鎖定在10個之類,大大提高了效率。
3、對於剪枝算法的優化,還有一點非常重要的就是對於節點的排序。考慮我們之前用到的圖。
如果順序是這樣的:
那麼我們就可以直接排除掉2個分支,這就是初略排序的威力。
這裏使用了插入排序的方式,來求得10個最大的點,並進行了排序。你也可以使用快排。但是一定要釋放內存。
這樣效率就能大大提升。
QVector<QPoint> Game::gen(int deep,int type) //返回可能選擇的點
{
int top= -1;
bool first =true;
QVector<node * > result(12);
for(int x = 0;x<15;x++)
{
for(int y= 0;y<15;y++)
{
if(chess[x][y]==0 && hasNeighbor(x,y))
{
int flag = 1;
for(int i= 0;i<=top;i++)
{
if(result[i]->point.x()==x && result[i]->point.y()==y)
{
flag=0;
break;
}
}
if(!flag)
{
continue;
}
node * snode = new node;
snode->point =QPoint(x,y);
int tmpscore = snode->sscore = evaluteScore2(QPoint(x,y),type);
//evaluteScore2是一個評估函數,和我之前策略表使用的判斷方法相似,就是判斷改點組成的所有五元組的得分
if(first)
{
result[0] =snode;
first=!first;
top=0;
}
else
{
if( top==9 && tmpscore <result[9]->sscore)
{
continue;
}
int tmp = top;
while(tmp>=0 && result[tmp]->sscore<tmpscore)
{
result[tmp+1] = result[tmp];
tmp--;
}
result[tmp+1] = snode;
top++;
if(top>9)
{
top=9;
}
}
}
}
}
QVector<QPoint> temp;
for(int i =0 ;i<10;i++)
{
if(i<=top)
temp.push_back(result[i]->point);
}
for(int i =0 ;i<=top;i++)
{
free(result[i]);
}
return temp;
}
總結:1、通過以上方式我進行6層的搜索也毫不費力,可以接受的時間內甚至可以達到8層。可以很輕鬆的戰勝低端玩家。
2、在QT中,一定要釋放內存,不釋放後果嚴重。我在開發中發現,由於這個gen函數會一直在min-max算法的遞歸中調用,我下一步棋如果不釋放內存,就會增加200M。
3、優化的3種方式:距離、預先評估該點,排序。