极小-极大和负值最大搜索算法的个人理解

最近过年放假,想来时间比较充裕,于是研究了一下棋类游戏制作的原理。我们所熟悉的棋类游戏有五子棋、跳棋、象棋、围棋等,我们在制作这些棋类游戏时,如果是两个玩家对弈,则游戏代码很容易写出来,只要按照下棋规则设计程序即可。但如果是玩家与电脑对弈,则难度就很大了,因为电脑不可能像玩家一样那么智能,如果电脑真能和人一样聪明的话,那么电影<<终结者>>中的画面在我们的现实社会中就要重现了。

任何棋类游戏都要定义一棵有根的树,即博弈树,一个节点就代表下棋的一个局面,子节点就是这个局面再走一步可以到达的下一个局面。博弈树的一层就表示下棋一方的所有着子的棋局。如下图是一个井子棋游戏,偶数层代表了"×"下子的所有可能局面,奇数层代表了"Ο"下子的所有可能局面。对弈双方轮流着子,某一方下子将会使的博弈树增加一层,直到某一方赢棋或是和棋(叶子节点),棋局结束时,博弈树不再增加。

在下棋时,对弈双方的目的就是将死对方,或者避免被将死,如果甲棋手在下子时能找到一步无论乙棋手怎么下子都会输的棋局,那么甲棋手一定会走这步堪称完美的棋法。下子的一方总是会这样考虑下棋:"如果我将子下在这里,对方会将子下在哪里使得他自已最有利,然后我又应该怎么下子使我最有利。。。",下子的一方会这样考虑许多不同的下子位置,然后选择一个对自已最有利,而使对方最不利的下子棋局。

如果棋手在考虑下棋时能够考虑到棋局结束时情况,也就是说他能够将上图中的那棵博弈树在脑海中完全展开,看到叶子节点,那他就一定能够赢得这盘棋,因为他对于棋局的所有变化都可以知道,任何局面他都可以保证找到一步最佳着法。其实井字棋的博弈树既不烦琐也不深,所以整个树可以遍历展开,但如果是国际象棋,每一局都有35个左右的合理着法,即一个节点有35个子节点,第一层是35个局面,两层就有35*35个局面,六层就接近二十亿个局面,而十层就超过两千万亿个局面了。想要展开这棵博弈树,这是任何一位象棋大师也不可能做到的,甚至动用最强大的计算机也不可能做到。

我们在下棋时其实只会考虑几步之后的棋局,这个时候棋局没有结束,肯定是看不出来谁赢谁输的,但是我们可以估计谁最有可能赢,至少我们可以估计赢的概率大不大,容不容易赢。初学者可能只能看1,2步之后的棋局,而高手则可以看几步,甚至十几步之后的棋局,也就是说高手可以将博弈树展开的更大更深。在程序中,我们会用"评价函数"来估计棋局的好与坏,如果甲方赢棋或者很有希望赢,那么评价函数通常会返回正数;如果乙方是赢棋或者很有希望赢,那么返回负数;如果棋局是均势或者是和棋,那么返回在零左右的数值。

如下图所示,我们设定

评价值总是站在"×"的立场来看的,"×"总是希望棋局到达评价值大的局面,评价值越大,表明"×"越有优势,图中+1000表明优势最大("×"赢);而"Ο"总是希望棋局到达评价值小的局面,评价值越小,表明"×"越不利,其实就是"Ο"越有优势,图中-1000表明优势最小("×"输,即"Ο"赢)。

对于人机博弈的程序,程序主要趋向于遵循一个被称为极小极大的算法,又名MiniMax算法,是一种找出失败的最大可能性中的最小值的算法。该算法是一种零总和算法,即一方要在可选的选项中选择将其优势最大化的选择,而另一方则选择令对手优势最小化的方法。如上图可见,可以通过树状搜索找到棋局中对双方来说都最好的着法,即是MinMax算法的关键。另外由于博弈树无法全部展开,我们只能展开部分博弈树,即我们只能向前查看几步后的棋局,因此在极小极大算法中我们限制对博弈树估算的深度。在很多运行在标准PC硬件的国际象棋程序中,极小极大搜索的深度被限制在6层左右——包含了十亿个可能的博弈位置。超过这个层数会导致的分析博弈位置的耗时更长,这是不现实的。例如,以1百万/s的比率分析博弈位置,6层的深度需要耗费约一刻钟。


极小极大算法及参考图示:

int MinMax(int depth)

{

if (SideToMove() =="×") {       // 如果轮到"×"走棋

return Max(depth);                 // "×"是先下棋者,取最大评估值

} else {             // 如果轮到"Ο"走棋

  return Min(depth);                 // 取最小评估值

 }

}

int Max(int depth) {                      //取最大评估值函数

 int best = -INFINITY;                 // 初始化最优值best = 负无穷大

 if (depth <= 0) {

  return Evaluate();                // 返回向前走depth步之后棋局的评价值

 }

GenerateLegalMoves();         // 生成当前"×"所有合理的走法

while (MovesLeft()) {              // 遍历"×"的所有走法

  MakeNextMove();               // 执行走法

val = Min(depth - 1);// 取"Ο"走子的最小评估值

  UnmakeMove();                 // 撤销走法

if (val > best) {                   // 获取"×"的最大评估值

   best = val;

  }

 }

 return best;                          // 返回best最大值

}

int Min(int depth) { // 取最小评估值函数

int best = INFINITY;            //初始化最优值best = 正无穷大,注意这里不同于“最大”算法

 if (depth <= 0) {

return Evaluate();// 返回向前走depth步之后棋局的评价值

 }

GenerateLegalMoves();// 生成当前"Ο"所有合理的走法

while (MovesLeft()) {  // 遍历"Ο"的所有走法

MakeNextMove();// 执行走法

val = Max(depth - 1);// 取"×"走子的最大评估值

UnmakeMove();// 撤销走法

if (val < best) {            //获取"Ο"的最大评估值,注意这里不同于“最大”算法

   best = val;

  }

 }

return best;// 返回best最小值

}


上面的代码可以这样调用:

val = MinMax(5);

这样可以返回当前局面的评价,它是向前看5步的结果。

上面的算法,大家可以参照下面的图加以理解。

图1(向前走2步的搜索树)

图2(向前走3步的搜索树) 

负值最大搜索:

负值最大只是对最小-最大的优化,它的代码更短,同时减少了移植代码时出错的可能,代码维护起来也比较方便。一个局面对"×"的优势为D,那么对"Ο"的优势就是-D;一个局面对"×"的优势为-D,那么对"Ο"的优势就是D。在负值最大算法中,没有了最小值,只有最大值,算法中用一个求最大值的操作代替了最小值和最大值交替的过程。需要注意的是,为了能使负值最大搜索算法得到正确的评价,必须修改局面评价函数的返回值,原来在极大极小搜索算法中始终返回的是"×"的优势,现在要改为当前走棋方的优势,占优返回正数,反之返回负数,最后这个值返回后必须加上负号,因为返回以后就是对另一方而言了。

负值最大算法及参考图示:

int NegaMax(int depth) {                   // 负值最大函数,求最大值的操作代替了最小值和最大值交替的过程

int best = -INFINITY;                      //初始化最优值best = 负无穷大

 if (depth <= 0) { 

return Evaluate();                     //返回向前走depth步之后,要走子一方的评价值

 }

GenerateLegalMoves();               //生成要走子一方所有合理的走法

while (MovesLeft()) {// 遍历要走子一方的所有走法

MakeNextMove();// 执行走法

val = -NegaMax(depth - 1);      // 获取另一方的评价值,注意这里有个负号

UnmakeMove(); // 撤销走法

if (val > best) {                         // 获取走子一方的最大评价值

   best = val;

  }

 }

return best;// 返回best最大值

}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容