Minimax算法
Minimax 算法又叫极小化极大算法,是一种找出失败的最大可能性中的最小值的算法。(维基百科)
alpha-beta 剪枝
Minimax算法中,由于每个节点都是取的极值,所以可以利用这个性质进行剪枝。
以上是简介,下面通过井字棋这个游戏,按照
DFS -> Minimax -> a - b剪枝
这样的顺序进行引导
一、需要解决的问题
井字棋中,我们要做到电脑每一步棋都是下在所有他可以下的位置中对他最有利的位置。
也就是Minimax算法的找出失败的最大可能性中的最小值,换句话也就是找出当前可以做的动作中对自己最有利的动作。
二、DFS -> Minimax
对于井子棋中,当我们考虑这一步应该下在那个位置上的时候,是基于当前局面进行考虑的,所以现在假设当前局面是初始局面,这时候还没有下棋。
那么我们可以下的位置有9个,然后对于我们下的每一种情况,形成一个新的局面,然后到了对方下棋,对于每一个分支在对方看来又有8种情况,又形成很多局面。下面给出部分图示,没有涉及到全部情况:
那么对于全部的图示,可以形成一颗搜索树,最后的叶子节点就是平局和某一方赢了的全部局面。
那么可以想到,对于上面的图示,是可以用DFS实现的。
虽然DFS可以通过遍历所有情况找到很多条可以使得最后局面达到我方不输或者赢下比赛的路线,但是这是一场博弈,搜索树中牵扯到了对方的下棋位置,对方是不可能配合我们的。
所以我们应该在DFS的同时,求出当前来说我们可以下的所有位置中,对我们最有利的位置,而不是对我们有利的位置中的随便一个位置。
这时候,Minimax算法就能解决这个问题,它在搜索上加上了博弈。
所以
Minimax = 搜索 + 博弈
首先Minimax算法第一步就是规定一下怎样把一个局面映射成一个值,而这个值代表的是对于当前下棋者他到达这个局面的时候的赢面大小。
值越大,赢面越大。
然后我们需要映射的局面只有,最后叶子节点的局面,也就是某一方胜利,或者平局的界面。
以下的规定都是在搜索我方现在应该下棋的位置的基础上:
1.当当前局面为我方胜利的时候,对应的值为空格数 +1
2.当当前局面为对方胜利的时候,对应的值为-(空格数+1)
(因为对方胜利的局面,对于我们来说是极其不利的所以取负数)
3.平局,对应的值为0
当这样规定之后,叶子节点都会对应的一个值。
然后,在上图中可以发现,奇数层是我方在下棋,偶数层是到了对方下棋。
假设现在是在奇数层,并且他的所有子儿子的局面赢面大小值也已经求出来了。
那么奇数层作为我方下棋,并且现在是在搜索我方的位置,所以奇数层的父节点,都会选择各自子儿子节点中的赢面最大的那个局面。
假设现在是偶数层,并且他的所有子儿子的局面赢面大小值也已经求出来了。
由于这个树的根节点是我方下棋的时候,也就是第1层为奇数层,也就代表着,根节点会选择所有子儿子中赢面最大的。那么作为对方肯定会想办法减少我们的赢面值,那么他在每次选择的时候,肯定会选择所有子儿子中赢面最小的,从而达到影响我们最后可以取到的最大值。
那么结论就是:
当为奇数层的时候,选择所有子儿子中的最大值
当为偶数层的时候,选择所有子儿子中的最小值
那么最后我们根节点所有子儿子中最大值对应的那个下法,就是我们在对方极致干扰的情况下,赢面最大的下法。
以上就是Minimax算法的思想
在算法实现的时候,就是DFS的同时,处理奇偶层的取值,和赢面值的返回。
/*
井字棋
OXO
OOO
OOO
X代表玩家,O代表电脑
从左上角开始编一维下标为0 ~ 8,二维下标为(0,0)~(2,2)
一维位置转化成2维公式
设t为1维下标,(x,y)为2维下标
x = t / 3,y = t % 3
t = x * 3 + y;
*/
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstdlib>
#include <cstring>
#include <algorithm>
using namespace std;
char board[3][3];//棋盘
int player;//记录当前是玩家还是电脑在下棋,0代表玩家,1代表电脑
void init()//初始化棋盘
{
for(int i = 0;i < 3;i ++)
for(int j = 0;j < 3;j ++) board[i][j] = '_';
}
void draw_board()//画棋盘
{
system("cls");
for(int i = 0;i < 3;i ++)
{
for(int j = 0;j < 3;j ++)
printf("%c ",board[i][j]);
puts("");
}
}
int is_Win()//判断谁赢了
{
for(int i = 0;i < 3;i ++) //判断行列
{
int x = board[i][0] != '_' && board[i][0] == board[i][1] && board[i][1] == board[i][2];//行
int y = board[0][i] != '_' && board[0][i] == board[1][i] && board[1][i] == board[2][i];//列
if(x) return (board[i][0] == 'X' ? 0 : 1);
if(y) return (board[0][i] == 'X' ? 0 : 1);
}
if(board[0][0] == 'X' && board[1][1] == 'X' && board[2][2] == 'X' || board[0][2] == 'X' && board[1][1] == 'X' && board[2][0] == 'X') return 0;
if(board[0][0] == 'O' && board[1][1] == 'O' && board[2][2] == 'O' || board[0][2] == 'O' && board