QT五子棋详解之八:min-max优化的负极大值算法兼谈qt内存泄露

QT五子棋详解之八:min-max优化的负极大值算法兼谈qt内存泄露


负极大值搜索作为min-max博弈算法的优化,其本质上没有任何区别。

负极大值搜索它的效率也不会快多少,只是它的代码更加优雅简洁。更好进行代码的移植。

我们将min-max博弈算法与负极大值算法的伪代码发出来比较一下。不加那个剪枝的

int MinMax(int depth) {
if (SideToMove() == WHITE) { // 白方是“最大”者
return Max(depth);
}
else {           // 黑方是“最小”者
return Min(depth);
}
}


int Max(int depth) {
int best = -INFINITY;
if (depth <= 0) {
return Evaluate();
}
GenerateLegalMoves();
while (MovesLeft()) {
MakeNextMove();
val = Min(depth - 1);
UnmakeMove();
if (val > best) {
best = val;
}
}
return best;
}


int Min(int depth) {
int best = INFINITY; // 注意这里不同于“最大”算法
if (depth <= 0) {
return Evaluate();
}
GenerateLegalMoves();
while (MovesLeft()) {
MakeNextMove();
val = Max(depth - 1);
UnmakeMove();
if (val < best) {  // 注意这里不同于“最大”算法
best = val;
}
}
return best;
}
上面的代码可以这样调用:
val = MinMax(5);

对比负极大值算法的伪代码是不是代码更加风骚:

QT五子棋详解之八:min-max优化的负极大值算法兼谈qt内存泄露

要从极大极小值过渡到负极大值搜索,需要理解几个方面:

1、极大极小值对每一个点的评估都是对于电脑来说的。而负极大值是对于本层来说的。如第一层是黑棋,那么这一层的分数是对于黑棋来说的,下一层是白棋层,那么这些点的分数是对于白棋的角度来看的。

2、max(deep-1)=-min(deep-1).假设本层为黑棋层,求本层某个点对于黑棋的分数,实际是求下一层白棋对于下一点的分数的负数。

3、每一层都是选的最大值,因为这个值是当前玩家的角度对于局势的评价。

看图说话:在这个局面,下面的点的得分是白棋对于局面的评价。



QT五子棋详解之八:min-max优化的负极大值算法兼谈qt内存泄露

那么对于黑棋来说,上面的点的得分又是多少呢?

首先对于白棋来说,其会选择对其最有利的一个点:

QT五子棋详解之八:min-max优化的负极大值算法兼谈qt内存泄露

黑棋对其取负数,就变成了黑棋对这个局势的评价:


QT五子棋详解之八:min-max优化的负极大值算法兼谈qt内存泄露


所以最后黑棋也会选择对其最大的值-3。

也就是说在每一层,都会选择最大的一个值,因为这个值是当前玩家的角度对于局势的评价。

接下来,看一下加入了剪枝的负极大值的伪代码:

QT五子棋详解之八:min-max优化的负极大值算法兼谈qt内存泄露

下图中的最后的1节点直接就被减掉了,因为最大的节点-3去取负数为3。而6大于3,就此节点就废了,因为白棋一定会选择6胡大于6的值。再去了反就是-6不会比-3大,而已经有-3这个节点了,所以对于黑棋来说,这个节点就废掉了。

QT五子棋详解之八:min-max优化的负极大值算法兼谈qt内存泄露


最后看一下我qt的代码:

多加了maxmin是因为我想对其进行一些拓展,核心是对于negamax函数的理解。
唯一使算法感到复杂的是,AlphaBeta是不断互换的。当函数递归时,AlphaBeta不但取负数而且位置交换了,但是可以证明它只是比最小-最大算法更好而已。我已经把这个算法的精髓用图展现出来了。

QPoint Game::maxmin(int deep)
{
    int alpha = -INT_MAX;
    int beta =  INT_MAX;
   QVector<QPoint> points =  gen(deep,computerColor);


   for(int i = 0;i<points.count();i++)
   {
       chess[points[i].x()][points[i].y()] = computerColor;
            int val =-negamax(deep-1,-beta,-alpha,points[i],computerColor==4?5:4);
            if(val>alpha)
            {
                alpha = val;
               resultbestpoint = points[i];
            }
        chess[points[i].x()][points[i].y()] = 0;
   }

     QVector<QPoint>().swap(points);
    Q_ASSERT(points.capacity() == 0);
  qDebug()<<"resultbestpoint"<<resultbestpoint.x()<<" "<<resultbestpoint.y()<<endl;
   return resultbestpoint;
}

int  Game::negamax(int deep,int alpha, int beta,QPoint point,int role)
{
    if(deep <= 0 || ifWin(point.x(),point.y()))
    {

        int k =evalute(role);
        return k;
    }
    QVector<QPoint> points = gen(deep,role);
    for(int i = 0;i<points.count();i++)
    {
        chess[points[i].x()][points[i].y()] = role;
        int v = -negamax(deep-1,-beta,-alpha,points[i],role==4?5:4);
        chess[points[i].x()][points[i].y()] = 0;
             if(v>=beta)
             {
               return beta;
             }

             if(v >alpha)
             {
                alpha = v;
             }
        }
    QVector<QPoint>().swap(points);
   Q_ASSERT(points.capacity() == 0);
        return alpha;
    }

最后谈一下qt的内存泄漏,

1、主要是对于vector来说的,其使用clear()函数不会释放内存,需要使用如下方式释放内存。

一定要注意,特别是像我做的8层搜索,会有上千万的节点的时候。内存的泄露是致命的问题。

QVector<T> v ...;
  QVector<T>().swap(v);
  Q_ASSERT(v.capacity() == 0);

2、vector中一定要存指针。

3、函数返回的时候,尽量不要直接返回vector