α-β剪枝算法

一、前言

        理解了很久α-β剪枝过程,特写此篇加深理解,并讲清楚算法的具体流程。如有错误、建议和优化欢迎指出!

二、博弈

(1)前提

  • 双人博弈:博弈对象只有己方和对方,没有第三方
  • 二人零和:若一方获胜取得x分,则另一方失败取得-x分
  • 全信息:博弈过程中,任何一方都完全了解当前格局和过去历史,不存在信息差
  • 非偶然:任何一方都只能根据当前实际情况来采取行动,不存在“碰运气”的行为
  • 利益完全对立:任何一方动作的目的都只能是击败对方,不存在暂时合作
  • 交替行动:每方一次只能进行一次动作,接着就轮到对方做动作

(2)博弈树

  • 根结点:博弈的初始状态
  • 叶子结点:博弈的终止状态
  • 边:可能的行动
  • 效益值:每个终止状态对一方的有利程度和对另一方的不利程度
  • 估值函数e(p):根据当前状态p和博弈规则,给出当前结点p的效益值
  • 深度:计算当前状态的效益值所需要向下参考的子树层数

三、极大极小分析法

零和:若己方的效益值为x则对方的效益值为-x,因此只需考虑其中一方的的效益值

倒推法:自底向上,根据子结点的效益值确定父结点的效益值,且叶子结点的效益值是已知的

博弈目的:若将己方作为根结点要获胜,则己方每次选择都要追求最大的效益值,而对方每次选择都要追求最小的效益值

  • MAX结点:己方做选择的层的全部结点,总是选择效益值最大的子结点-->对己方最有利(极大的含义)
  • MIN结点 :对方做选择的层的全部结点,总是选择效益值最小的子结点-->对己方最不利(极小的含义)

四、α-β剪枝

(1)基本概念

  • 极大极小算法的局限性:必须算出全部子结点结点的效益值才能判断父节点的效益值 
  • 极大极小算法的优化:及时剪掉无用的子结点分枝,仅根据部分子结点就可以得到父节点的效益值
  • MAX结点:
    • α值:MAX结点的子结点的最大值
    • β值:MAX结点的左边/已遍历兄弟结点的最小值
  • MIN结点:
    • α值:MIN结点的左边/已遍历兄弟结点的最大值
    • β值:MIN结点的子结点的最小值

(2)α剪枝--删除以MAX结点为根结点的分支

剪枝原理:MAX结点选择最大值,但MAX结点的父结点是MIN结点要选择最小值,如果存在α>β即该MAX结点最终取值一定≥α,因此MIN结点会暂时选择β分支,而不会考虑α分支(所以才叫α-cuts)

(3)β剪枝--删除以MIN结点为根结点的分支 

剪枝原理:MIN结点选择最小值,但MIN结点的父结点是MAX结点要选择最大值,如果存在α>β即该MIN结点最终取值一定≤β,因此MAX结点会暂时选择α分支,而不会考虑β分支(所以才叫β-cuts)

 (4)α-β剪枝算法分析

        1. 算法的操作

  1. 初始化:令根节点的α=-∞,β=+
  2. 传递(从上往下)将父结点α值和β值都赋给子结点
  3. 回溯(从下往上)父MAX结点的α值 = 子MIN结点的β值,父MIN结点的β值 = 子MAX结点的α值
  4. 剪枝(从左往右)如果结点更新后的α≥β,则剪去以该结点为根结点的分支

        2. 操作的理解(赋值原理)

  • 回溯的单一赋值:MAX的β值与子结点无关,MIN的α值与子结点无关
  • 回溯的交叉赋值:
    • 父MAX的α值取决于子MIN,子MIN根据β取值
    • 父MIN的β值取决于子MAX,子MAX根据α取值
  • 传递的对应赋值:
    • MAX的子结点最大值 = 子结点已遍历兄弟结点的最大值 --> 父MAX结点的α值与子MIN结点的α值是相同的
    • MIN的子结点最小值 = 子结点已遍历兄弟结点的最小值 --> 父MIN结点的β值与子MAX结点MAX的β值是相同的
  • 虽然MAX和MIN结点的α值和β值的意义不一样,但是观察发现上述α-cut和β-cut的前提都是α≥β

        3. 算法的流程(详细图解)

  1. 令根结点的α = +∞,β = -∞,然后逐渐向下传递,直到遇到第一个叶子结点
  2. 反复进行以下三步:
    1. 传递:根据父结点更新子结点 -> 成对对应赋值
    2. 回溯:根据子结点更新父结点 -> 单一交叉赋值
    3. 剪枝:一旦出现α ≥ β的情况,及时剪枝
  3. 得到根结点的效益值,并选择对应的子结点作为下一次扩展的根结点

(5)α-β算法的伪代码

全局设置最大深度maxdepth

主函数调用 alpha-beta(node = root, a = -∞, β = +∞, flag = 'MAX', depth = 0)

  • 如果当前结点到达最大深度,即depth == maxdepth:返回当前结点的评估函数值
  • 如果当前结点是最优状态,返回+∞
  • 如果当前结点时最坏状态,返回-∞
  • 如果当前结点是MAX结点,即flag = 'MAX':
    1. α = 传递值,β = 传递值
    2. 遍历每个子结点subnode:
      1. 调用 alpha-beta(subnode, a, β, 'MIN', depth+1),得到返回值x
      2. 若x > α,则更新α = x,node.next = subnode
      3. 若α ≥ β,则break循环
    3. 返回α
  • 如果当前结点是MIN结点,即flag == 'MIN':
    1. α = 传递值,β = 传递值
    2. 遍历每个子结点subnode
      1. 调用 alpha-beta(subnode, a, β, 'MAX', depth+1),得到返回值x
      2. 若x < β,则更新β = x,node.next = subnode
      3. 若α ≥ β,则break循环
    3. 返回β

五、 α-β算法的通用代码(最重要)

class Node:
    # 初始化数据成员
    def __init__(self,state,side):
        self.state = state      # 当前状态
        self.next = None        # 目标状态
    # 获取当前状态的全部子状态
    def get_substates(self,nextside):
        pass
    # 获取当前节点对应状态的目标价值
    def evaluate(self):
        pass
    # 检查是否是最佳状态或最坏状态
    def check(self):
        pass

def alpha_beta_cut(node:Node,alpha,beta,turn,depth,max_depth):
    flag = node.check()
    if flag == 最佳状态:
        return float('inf')
    elif flag == 最坏状态:
        return float('-inf')
    elif flag == 持平状态:
        return 0
    elif depth == max_depth:
        return node.evaluate()
    
    if turn == 'MAX':
        node_alpha,node_beta = alpha,beta
        for substate in node.get_substates(turn):
            subnode = Node(substate)
            value = alpha_beta_cut(subnode,node_alpha,node_beta,'MIN',depth+1,max_depth)
            if node_alpha < value:
                node_alpha = value
                node.next = substate
            if node_alpha >= node_beta:
                break
        return node_alpha

    if turn == 'MIN':
        node_alpha,node_beta = alpha,beta
        for substate in node.get_substates(turn):
            subnode = Node(substate)
            value = alpha_beta_cut(subnode,node_alpha,node_beta,'MAX',depth+1,max_depth)
            if node_beta > value:
                node_beta = value
                node.next = substate
            if node_alpha >= node_beta:
                break
        return node_beta

initial_state = [初始状态]
root = Node(initial_state)
nextstep = alpha_beta_cut(root,float('-inf'),float('inf'),'MAX',0,3)

六、案例分析:井字棋

假设AI先走x,人后走o

如果需要自定义对局双方是AI还是人,或者自定义AI先走还是人先走,可参考我的另一篇详细介绍井字棋实现的博客

http://t.csdnimg.cn/NeI7yicon-default.png?t=N7T8http://t.csdnimg.cn/NeI7y

'''
棋盘设置:
0 1 2
3 4 5
6 7 8
'''

import math

class Node:
    def __init__(self,state):
        self.state = state
        self.next = None
    
    def check(self):
        # 检查行
        for i in range(0, 9, 3):
            if self.state[i] == self.state[i + 1] == self.state[i + 2] and self.state[i] != '_':
                return self.state[i]
        # 检查列
        for i in range(0, 3, 1):
            if self.state[i] == self.state[i + 3] == self.state[i + 6] and self.state[i] != '_':
                return self.state[i]
        # 检查对角线
        if self.state[0] == self.state[4] == self.state[8] and self.state[0] != '_':
            return state[0]
        if self.state[2] == self.state[4] == self.state[6] and self.state[2] != '_':
            return self.state[2]
        # 检查平局
        if '_' not in self.state:
            return 'Draw'
        return 'Continue'

    def evaluate(self):
        num_Xwin,num_Owin = 0,0
        #判定行
        for i in range(0,9,3):
            row = self.state[i:i+3]
            if 'o' not in row:
                num_Xwin += 1
            if 'x' not in row:
                num_Owin += 1
        #判定列
        for i in range(0,3):
            col = [self.state[i],self.state[i+3],self.state[i+6]]
            if 'o' not in col:
                num_Xwin += 1
            if 'x' not in col:
                num_Owin += 1
        #判断对角线
        diag1 = [self.state[0],self.state[4],self.state[8]]
        diag2 = [self.state[2],self.state[4],self.state[6]]
        if 'o' not in diag1:
            num_Xwin += 1
        if 'o' not in diag2:
            num_Xwin += 1 
        if 'x' not in diag1:
            num_Owin += 1
        if 'x' not in diag2:
            num_Owin += 1

        return num_Xwin - num_Owin 
    
    def get_substates(self,player):
        substates = []
        for i in range(9):
            if self.state[i] == '_':
                substate = list(self.state)
                substate[i] = player
                substates.append(substate)
        return substates
    
def alpha_beta_cut(node:Node,alpha,beta,type,depth,max_depth):
    if node.check() == 'x':
        return float('inf')
    if node.check() == 'o':
        return float('-inf')

    if depth == max_depth:
        return node.evaluate()
    
    if type == 'MAX':
        node_alpha,node_beta = alpha,beta
        for substate in node.get_substates('x'):
            subnode = Node(substate)
            value = alpha_beta_cut(subnode,node_alpha,node_beta,'MIN',depth+1,max_depth)
            if node_alpha < value:
                node_alpha = value
                node.next = substate
            if node_alpha >= node_beta:
                break
        return node_alpha

    if type == 'MIN':
        node_alpha,node_beta = alpha,beta
        for substate in node.get_substates('o'):
            subnode = Node(substate)
            value = alpha_beta_cut(subnode,node_alpha,node_beta,'MAX',depth+1,max_depth)
            if node_beta > value:
                node_beta = value
                node.next = substate
            if node_alpha >= node_beta:
                break
        return node_beta

def Print(state):
    for i in range(0,9,3):
        print(state[i],state[i+1],state[i+2])


state = ['_'] * 9
root = Node(state)
player = 'AI'
while 1:
    flag = root.check()
    if flag == 'Draw':
        print('平局')
        break
    elif flag == 'x':
        print('x获胜')
        break
    elif flag == 'o':
        print('o获胜')
        break
    elif flag == 'Continue':
        if player == 'HUMAN':
            index = int(input('HUMAN走棋,请输入你要下的位置:'))
            root.state[index] = 'o'
            Print(root.state)
            player = 'AI'
            continue
        if player == 'AI':
            print("AI走棋:")
            alpha_beta_cut(root,float('-inf'),float('inf'),'MAX',0,3)
            root.state = root.next
            Print(root.state)
            player = 'HUMAN'
            continue

七、效率分析

令分支系数=b(即一个结点的平均子结点个数);搜索树深度=d

  • 极大极小过程的时间复杂度:O(bd)
  • α-β过程的时间复杂度:O(bd/2)-->在相同代价下,α-β过程是极大极小过程向前看的走步数的两倍

搜索效率与选取的α、β值和最终倒退值的相似度有关,如果α值和β值的选取越接近最终倒退值,剪掉的分支就越多,搜索效率越高

八、参考资料

http://t.csdnimg.cn/VH7LM

http://t.csdnimg.cn/HD50W

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值