博弈树搜索技术(Minimax算法,ɑ-β 算法)

博弈树搜索技术(Minimax算法,ɑ-β 算法)

一、 算法的理解

  1. Minimax算法

概括:算法可以概括为——己方利益最大化,对方利益最小化”。 即一方要在可选的选项中选择将其优势最大化的选择,而另一方则选择令对手优势最小化的方法。
实现方法:它使用了简单的深度递归搜索技术来确定每个节点的值:它从根节点开始,一直前进到叶子节点,然后在递归回溯时,将叶子节点的效用值往上回传——对于MAX 方,计算最大值,对于MIN 方,计算最小值。
def Minimax(node, depth, player):参数分别为(根节点,深度,玩家)
if depth == 0 or node is a terminal node:
递归出口:节点没有子节点或者已能判断出胜负的时候
return the heuristic value of node
if player == True:
bestValue = -∞
当是标记为true(max方,要bestValue最大)的玩家时,先赋值一个最小值,为取最大值做准备。然后开始取孩子节点中的最大值。
for each child of node:
v = Minimax(child, depth-1, False)
bestValue = max(bestValue, v)在当前孩子节点 与 当前记录的最大值中取最大值。
return bestValue
返回所有孩子节点中的最大值
else:否则玩家是false方(mini方,要bestValue最小),先赋值一个最大值,为取最大值做准备。然后开始取孩子节点中的最大值。
bestValue = +∞
for each child of node:
v = Minimax(child, depth-1, True)
bestValue = min(bestValue, v) 在当前孩子节点 与 当前记录的最大值中取最小值。
return bestValue返回所有孩子节点中的最小值
 ɑ-β 算法
概括:ɑ-β 算法是在Minmax算法的基础上进行剪枝,从而提高搜索效率。Minimax 算法是基于深度优先的搜索,因此,任何时候只需要考虑树中某条路径上的节点。
 剪枝条件:
ɑ路径上发现的MAX 方的最佳值;β路径上发现的MIN 方的最佳值;在搜索的过程中,ɑ-β 算法不断地更新MAX 方的_ 值和MIN 方的ɑ值,并且一旦条件成熟时即进行剪枝:MAX 方发现回传值低于自己的当前最佳值,即进行β剪枝;MIN 方发现回传值高于自己的当前最佳值,即进行ɑ剪枝。
 ɑ-β 算法是在Minimax算法上进行改进的,算法的关键就在于剪枝。
结合伪代码着重讲一下剪枝
def alpha-beta(node, depth, alpha, beta, player):
if depth == 0 or node is a terminal node:
return the heuristic value of node
if player:
当是标记为true(max方)的玩家时,先赋值一个最小值,为取最大值做准备。然后开始取孩子节点中的最大值。
v = -∞
for each child of node:
v = max(v, alpha-beta(child, depth-1, alpha, beta, False))
alpha = max(alpha, v)
if beta <= alpha: MAX 方发现回传值低于自己的当前最佳值,即进行β剪枝。理解:mini方取最小值,所以mini方的取值范围为负无穷到beata,
max方取最大值,所以max的当前取值范围为malpha到正无穷。当beta下与alpha时,二者无交集,没有“商量”的余地,所以此时可以进行β剪枝
break #beta pruning
return v
else: MIN 方
v = +∞
for each child of node:
v = min(v, alpah-beta(child, depth-1, alpha, beta, True))
beta = min(beta, v)
if beta <= alpha: 道理和β剪枝类似。MIN 方发现回传值高于自己的当前最佳值,即进行ɑ剪枝。
break #alpha pruning
return v

#minimax
def Minimax(node, depth):
    result=node.isGameOver()
    if result!=None:
        return result,(),depth
    if node.getPlayer()==TicTacToe.BLACK:
        bestValue=-sys.maxsize
        bestmove=()
        bestdepth=sys.maxsize
        moves=node.getAllMoves()
        for move in moves:
            child=node.clone()
            child.play(*move)
            v,_,leafDepth=Minimax(child,depth+1)
            if bestValue<v:#为什么不是<=
                bestValue=v
                bestmove=move
                bestdepth=depth
            if bestValue==v and bestdepth>leafDepth:
                bestValue=v
                bestmove=move
                bestdepth=depth
        return bestValue,bestmove,bestdepth
    else:
        bestValue=sys.maxsize
        bestmove=()
        bestdepth=sys.maxsize
        moves=node.getAllMoves()
        for move in moves:
            child=node.clone()
            child.play(*move)
            v,_,leafDepth=Minimax(child,depth+1)
            if bestValue>v:
                bestValue=v
                bestmove=move
                bestdepth=depth
            if bestValue==v and bestdepth>leafDepth:
                bestValue=v
                bestmove=move
                bestdepth=depth
        return bestValue,bestmove,bestdepth
#AlphaBeta
def AlphaBeta(node, depth,alpha,beta):
    result = node.isGameOver()
    if result != None:
        return result, (), depth
    if node.getPlayer() == TicTacToe.BLACK:
        bestValue = -sys.maxsize
        bestMove = ()
        bestDepth = sys.maxsize
        moves = node.getAllMoves()
        for move in moves:
            child = node.clone()
            child.play(*move)
            v, _, leafDepth = AlphaBeta(child, depth+1,alpha,beta)
            if bestValue == v and bestDepth > leafDepth:
                bestValue = v
                bestMove = move
                bestDepth = leafDepth
            if bestValue < v:
                bestValue = v
                bestMove = move
                bestDepth = leafDepth
            alpha = max(alpha, bestValue)
            if beta<=alpha:
                break
        return bestValue, bestMove, bestDepth
    else:
        bestValue = sys.maxsize
        bestMove = ()
        bestDepth = sys.maxsize
        moves = node.getAllMoves()
        for move in moves:
            child = node.clone()
            child.play(*move)
            v, _, leafDepth = AlphaBeta(child, depth,alpha,beta)
            if bestValue == v and bestDepth > leafDepth:
                bestValue = v
                bestMove = move
                bestDepth = leafDepth
            if bestValue > v:
                bestValue = v
                bestMove = move
                bestDepth = leafDepth
            beta = min(beta, bestValue)
            if beta<=alpha:
                break
        return bestValue, bestMove, bestDepth

二、 运行结果(截图)

Minimax算法

在这里插入图片描述

ɑ-β 算法
在这里插入图片描述

三、 碰到的问题与解决办法

 Minimax算法
实现过程中逻辑不够严密:
对于所有的孩子节点,在“择优”时,依据我的理解判断条件是bestValue<V(当前孩子节点的值)或者是bestValue<=都可以,因为选取的依据就是值的大小。
严谨正确的理解:通过分析老师的代码,判断的依据不仅仅是值的大小,还有节点的深度。当值有优势的时候,该节点被选中一定是没有疑问的。然而出现两个节点值相等时,就以深度为依据,选择深度小的节点。
 ɑ-β 算法

由于我是在Minimax算法的基础上进行修改来实现ɑ-β 算法。首先是修改函数名,然后分别在max方和mini方中加入剪枝操作。剪枝操作比较简单,只需要两行代码。
这个问题能成功的编译和运行,在下第一步棋后就一场退出,提示如上错误。根据提示找到相对应的代码,网上搜索分析后,判断递归可能进入死循环,分析:到子节点时depth的值没有进行加一,在value值相等时,那些深度较小的节点被忽略,导致找不到“最大利益”,从而导致递归无出口。

四、 阶段总结

minimax算法通过递归的方式,从当前状态开始搜索,一直到游戏结束,然后回溯,在搜索时分别使两方的利益最大化,两方之间相互抗衡。但是当深度变深时,博弈树的节点数量就会指数爆炸。
依据A* 算法的原理,该算法从来不会扩展那些搜索代价大于f_ 的节点。实际上,从树搜索的角度看,这就是一种剪枝(Pruning)——这些节点及其分支没有得到扩展的机会,而被整个地剪掉了。对于minimax中双方不对最大化自己利益的过程中,出现的二者“不可商量”的情况进行舍弃。即ɑ-β 算中beta <= alpha,此时二者已无交集。对于这种情况进行剪枝,从而提高算法的效率。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嗯哼_Hello

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值