[搜索算法] 极大极小算法

目录

  • 01 极大极小算法的思维
  • 02 极大极小算法的雏形
  • 03 极大极小算法的进化

内容

01 极大极小算法的思维

​ 最初的人们开始下五子棋的时候,会选择当前局面对于自己优势高的方向发展。后来人们开始在这方面卷,开始投资未来,每一步棋都希望让接下来的几步棋成为高优势的方向发展,极大极小算法的思维就是在于此,这也比最初的思维高了不止一个层次。

02 极大极小算法的雏形

​ 极大极小的核心思维如下:

模拟下棋,得到下棋最有未来优势的点;

在模拟下棋的过程中:

​ 当自己下棋的时候,选择对于我们最有利的;

​ 当对手下棋的时候,选择对于我们最劣势的;

​ 假如你小气的哥哥要给你零花钱,他左右手拿着不同面值的钱币,让你选一个手,然后他挑一个钱币给你。假设:

​ 左手: 1 5 100

​ 右手: 10 20 50

​ 当然选择右手,是因为小气的哥哥一定会选择钱最少的给你,说白了其实就相当于在1块和10块里面选择。若假如你是哥哥,你现在必须给你讨厌的弟弟零花钱,你现在左右手有上面例子的钱,你得选一个手让弟弟选里面的钱,你讨厌的弟弟一定会拿钱最大的。那么很明显,你会选择右手兜。那么实际上:

在你和你哥的博弈过程中:

​ 当自己选择的时候,选择对自己最有利的;

​ 当对手选择的时候,选择对自己最劣势的;

​ 接下来介绍一下极大极小算法的基本知识:评估函数、模拟、深度

评估函数:评估函数的作用是评价一个棋局对于我们的优劣状态,返回评估得到的整数。

​ 当有了评估函数之后,利用返回的整数可以知道当前棋局对于我们的优劣,然后我们可以通过优劣进行相应处理。那么很高兴的是,极大极小算法通过评估函数的存在使得核心进化成了:

在模拟下棋的过程中:

​ 当己方下棋时,选择分数最大的;

​ 当敌方下棋时,选择分数最小的;

​ 假设现在棋盘上有一个黑棋四连,但是有一边被白棋堵住了。那么,假如下一手是白方,则当前的局势对于棋手来说很不利,是因为若这次没有白棋五连,不堵则失败了;假如下一手是黑方,则当前局势对于棋手来说非常有利。那么可得:同一个棋局对于敌我的评估函数不相同。

​ 假设黑白两方下棋。模拟到一定程度后便可以对模拟的棋局进行评估,得到对应的评估分数。

模拟:模拟的深度数越深,则代表模拟的次数越多,那么所考虑的结果也越多,所以可得:**模拟可以决定最终结果。**但实际上,对于一个五子棋棋局,模拟深度为1次时,可能需要200次左右,模拟深度为2时,则大概需要200*200次,模拟深度为3,4,5,则能够达到一个可怕的数字,计算时间需要达到几分钟、几个小时甚至几天。所以可得:比起模拟深度,模拟次数才是极大极小算法优化的最终形态的遍历控制。

深度:类似于树的结构,这里的深度代表模拟一次黑白下棋的次数。若读者学习过DFS(Depth First Search),那么可知:**深度可以方便我们进行程序的递归。**当深度为0时进行评估,直接返回评估分数。

​ 下面是极大极小算法的逻辑代码:

int MinMax(int depth) {//程序入口分流
	if (SideToMove() == Myself) return Max(depth);
	else return Min(depth);
}
int Max(int depth) {
	int best = -INFINITY;//系统最小值,这里可以换成分数不可达的下限
	if (depth <= 0) return Evaluate();//假如深度为0,则不能在模拟,返回当前局面的评估值
	GenerateLegalMoves();//得到当前棋局的符合规矩的下法的
	while (hasLegalNext()) {//若存在符合规矩的下一个走法
		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 (hasLegalNext()) {
		MakeNextMove();
		val = Max(depth - 1);
		UnmakeMove();
		if (val < best) best = val;
	}
	return best;
}

03 极大极小算法的进化

​ 很明显的是,上面的代码似乎有点笨重了,因为Min和Max函数存在很多相同的地方,那么可以优化的是——将这两个方法进化成一个方法。值得一提的是,虽然在代码的设计中,会尽量减少程序间的耦合程度,意思是尽量把两个功能写成两个方法,但实际上,当我们优化Min和Max其中一个方法的时候,另外一个方法也需要进行相同的优化,所以这里的合并是存在必要的。

​ 下面是负值极大算法逻辑代码:

int NegaMax(int depth) {
	int best = -INFINITY;
	if (depth <= 0) return Evaluate();
	GenerateLegalMoves();
	while (hasLegalNext()) {
		MakeNextMove();
		val = -NegaMax(depth - 1); // 注意这里有个负号,负号的存在使得不管你是哪一方,程序都求最大值即可。
		UnmakeMove();
		if (val > best) best = val;
	}
	return best;
}

​ 很明显的是,这里的评估函数需要进行黑白无差别化,因为这里的评估函数是对于子递归深度为0的当前方而评估的。下面举例:

  • 若深度为1,我方白棋,那么这里深度为0的时候就是针对的黑方:

    1. 深度为1,白方,递归;

    2. 深度为0,黑方,对于当前方(黑方)进行评估,返回评估分数;

    3. 深度返回1,白方,得到负值的评估分数,选取其中最大值,实则是,选取黑方的最小值;

  • 若深度为2,我方白棋,那么这里的深度为0的时候就是针对的白方:

    1. 深度为2,白方,递归;

    2. 深度为1,黑方,递归;

    3. 深度为0,白方,对于当前方(白方)进行评估,返回评估分数;

    4. 深度返回1,黑方,得到负数的评估函数,选择其中最大值,实则选取白方的最小值;

    5. 深度返回2,白方,得到正数的评估函数,选择其中最大值,实则选取黑方的最小值,选取白方的最大值;

​ 随着程序优化的情况下,两种情况的评估分数都是对于深度为0的当前方而言,则评估函数需要无差别化。

​ 每个棋的评估函数都存在不一样的差异:对于井字棋,其评估函数的无差别化很简单,直接进行棋盘的正负交换即可。对于简单五子棋,同样如此。但是值得一提的是,返回相反数并不会对评估函数有任何影响。笔者在对这里理解的时候总会将负数带入评估函数,但实际上,利用相反数只是为平衡当前方的措施,即求返回分值的最大值,和评估函数没有任何关系,也不会对当前的评估有任何影响。

​ 但是,若遍历并非是按照深度进行,而是按照遍历次数进行,那么对评估函数的要求更加严格。因为很有可能存在一种深度不平衡的情况,一部分递归较深,一部分递归较浅,那么若返回的分值相差奇数次,则在某一阶段得到的分值为一些是负数一些是正数,这里时候那些负数的就会被莫名地淘汰,反而失去了希望的效果。这个需要待笔者往后研究之后再商讨。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值