Alpha-Beta剪枝的原理的深入理解(无图预警)

这篇具有很好参考价值的文章主要介绍了Alpha-Beta剪枝的原理的深入理解(无图预警)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

转载请注明 原文链接 :https://www.cnblogs.com/Multya/p/17929261.html

考虑一个树:

一棵树上只有叶子节点有值,有确定的根节点的位置

根据层数来划分叶子节点和根节点之间的链接节点

偶数层上的值取子节点的最大值,奇数取最小

因为叶子节点上的值确定,在有这么个规则之后整棵树上所有节点就定下来了吧

现在我遮住全部叶子节点,让你通过打开尽量少次数叶子节点,确定根节点的值

我们通过alpha-beta 剪枝来实现

确定的事情:

  • 一个节点上的值必定是长在它身上的所有叶子的值中的一个
  • max{ a, min{b,x} } 如果b比a小,无论x取什么,结果都是a
  • min{ a, max{b,x} } 如果b比a大,无论x取什么,结果都是a

为什么? 我们放慢这个思考过程看看背后的逻辑

我们用一个区间来表示这个算式最后的结果的范围,下界是a,上界是b

我们知道计算最大最小的过程,其实就是一个单边的区间不断根据新的值刷新的过程:

假设计算max{4,5,1}

第一个数是4暂定是表达式的值

然后4确定下界是4的区间,这个区间希望得到一个在这个区间内的数刷新区间下界和更新表达式的值。5在这个区间内,所以它能刷新表达式和更新区间下界

1不在这个区间内,所以它不能更新表达式和区间。所以表达式是最后更新状态下的5,计算正确

刷新区间的操作也可以用求交集来实现,这样的话就省了判断的那一步,然后结果也可以用最后的下界来确定

所以可以变成这样:

求区间(4,+ \(\infty\) )∩(5,+ \(\infty\) )∩(1,+ \(\infty\) )的下界

这样我们就实现了用区间来求最大(最小)的功能

再看max{ 4, min{3,1} }

第一个数是4暂定是表达式的值

然后4确定下界是4的区间,这个区间希望得到一个在这个区间内的数刷新区间下界和更新表达式的值

这个区间希望表达式min{3,1}得到一个大于4的数刷新区间下界和更新表达式的值(这个过程区间原封不动传递下去)

先计算表达式min{3,1} 第一个数是3,然后5确定表达式在小于3的区间内,希望得到一个小于5的值刷新表达式

但是这个区间内所有的数都不在上个表达式期望的大于4的数区间内(区间不重合),也就是这个表达式所有可能的值都不能刷新上个表达式的值,所以跳过计算这个子表达式

由于上个表达式所有数遍历完了,最后更新的数是4,所以表达式值为4

我们再来看完全用区间来实现的方法:(【】内计算得到一个数)所有都是闭区间哈,意会就行

求(4,+ \(\infty\) )∩ ( min{3,1} ,+ \(\infty\) )的下界

即求(4,+ \(\infty\) )∩ (【(- \(\infty\) ,3)∩ (- \(\infty\) ,1)的上界】,+ \(\infty\) )的下界

我们定义空区间的上界是正无穷,下界是负无穷,这样做的理由是使空区间对结果不产生任何贡献(因为都是取交集)

然后是关键的一步:根据上面的启发,我们把前面得到的区间套一层壳,也就是:

等价为求(4,+ \(\infty\) )∩ (【(4,+ \(\infty\) )∩【(- \(\infty\) ,3)∩ (- \(\infty\) ,1)的上界】的上界】,+ \(\infty\) )的下界

这个结果不变。因为【(4,+ \(\infty\) )∩【(- \(\infty\) ,3)∩ (- \(\infty\) ,1)的上界】的上界】的结果不是空集的话对上界没有影响,是空集的话没有贡献。

可以等价为(4,+ \(\infty\) )∩ (【(4,+ \(\infty\) )∩(- \(\infty\) ,3)∩ (- \(\infty\) ,1)的上界】,+ \(\infty\) )的下界,因为取交集,先后没有影响。

此时可以先通过判断(4,+ \(\infty\) )∩(- \(\infty\) ,3)是空集来提前结束求值,得到最后区间(4,+ \(\infty\)

像上面这样,如果把传递给子表达式期望的区间和子表达式结果的区间看成一回事的话,那就是alpha-beta剪枝的逻辑。回到最开始的那个树。这个区间能被固定在每一个节点身上,表示这个节点的状态如果要刷新这个节点的值,要求新输入的值的区间范围。如果这个节点从未被刷新,那么这个节点的值就不会产生任何贡献来刷新上一个表达式的值。如果这个区间是一个空区间,那么所有的值都不能刷新这个节点的值,那么就没有必要继续给这个节点输入值了。

观察区间动向的话会发现有关区间的操作有以下几种:

  • 把值改写为区间
  • 区间取交集
  • 取区间一端的数传递回去
  • 把一个区间传递给子表达式内提前取交集

再看max{ 4, min{3,1} },这次我们加上树的形状和叶子节点遮挡的特性

自行画图:有4 3 1三个节点,一个根节点,两个链接节点,三个叶子节点

开始。打开叶子4,更新所连接的父节点

这里将每个节点区间初始化为全体实数,因为是MAX层,刷新这个节点的区间为(4,+ \(\infty\)

传递(4,+ \(\infty\) )给链接3和1的链接节点(此时我们还不知道3和1)

链接节点打开叶子3,因为是MAX层,产生区间(- \(\infty\) ,3)

原来已经有集合了,取交集为空集,提前结束运算,不做任何贡献,不传递值回去

这样就完成了任务,只打开了4和3就知道了根节点的值是4

如果把前面的值用负号在min层取反,那么所有的层的操作逻辑都变成一样的了

alpha-beta剪枝的算法的代码:

//意义:目前棋盘的最值评分 分数的正负取决于一开始的isBlackNow
int abSearch(int floor, int alpha, int beta, bool isBlackNow, Coord &searchResult) {
    int tmpScore, moveCount = 0;
    Coord tempSearchResult{};
    //优化1
    std::vector<ScoreCoord> possibleMove = generatePossibleMove(isBlackNow);
    for (auto &now: possibleMove) {
		//优化2
        moveCount++;
        if (moveCount > 8) break; //只搜索前8个可能的落子点
        int x = now.coord.x, y = now.coord.y;
        m_map[x][y] = isBlackNow ? BLACK_CHESS : WHITE_CHESS;
        //优化3
        if (someoneWin({x, y})) {//如果有人赢了 必定是下这个子的人赢了
            searchResult = {x, y};
            tmpScore = evaluateAll(isBlackNow);//返回这个局面最高的得分,也就是赢局的分数
            m_map[x][y] = NO_CHESS;
            return tmpScore;
        }
        //单层搜索
        if (floor == 1) {//如果只看这一步子 那就是这一步子所有可能的得分中的最大值
            tmpScore = evaluateAll(isBlackNow);
            m_map[x][y] = NO_CHESS;
            if (tmpScore > alpha) {
                alpha = tmpScore;
                searchResult = {x, y};
            }
            continue;
        }
        tmpScore = -abSearch(floor - 1, -beta, -alpha, !isBlackNow, tempSearchResult);//不然得分就是我下了之后的对方的所能得到的最高分取负
        m_map[x][y] = NO_CHESS;
        if (tmpScore >= beta) {
            return beta;
        }
        if (tmpScore > alpha) {//取对方尽所有努力后得到最大值中的最小的一个 取负值后变成最大的一个
            alpha = tmpScore;
            searchResult = {x, y};
        }
    }
    return alpha;
}

抽象出来的伪代码:文章来源地址https://www.toymoban.com/news/detail-760789.html

//意义:目前棋盘的最值评分 分数的正负取决于一开始的isBlackNow
int abSearch(int floor, int alpha, int beta, bool isBlackNow, Coord &searchResult) {
    possibleMove = generatePossibleMove();
    for (auto &now: possibleMove) {
	    downOneStep();
        if (someoneWin()) {//如果有人赢了 必定是下这个子的人赢了
            saveSearchResult();
            restoreLastStep();
            return evaluateScore();
        }
        //单层搜索
        if (floor == 1) {//如果只看这一步子 那就是这一步子所有可能的得分中的最大值
            tmpScore = evaluateScore();
            restoreLastStep();
            if (tmpScore > alpha) {
                alpha = tmpScore;
                saveSearchResult();
            }
            continue;
        }
        tmpScore = -abSearch(floor - 1, -beta, -alpha, !isBlackNow, tempSearchResult);//不然得分就是我下了之后的对方的所能得到的最高分取负
        restoreLastStep();
        if (tmpScore >= beta) {
            return beta;
        }
        if (tmpScore > alpha) {//取对方尽所有努力后得到最大值中的最小的一个 取负值后变成最大的一个
            alpha = tmpScore;
            saveSearchResult();
        }
    }
    return alpha;
}

到了这里,关于Alpha-Beta剪枝的原理的深入理解(无图预警)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 单元测试、冒烟测试、集成测试、系统测试、回归测试、验收测试、Alpha、Beta

    1.冒烟测试 代码跑通即可。 这一术语源自硬件测试:测试一个硬件或硬件组件时,先直接加电,如果冒烟了,则无需进行后续测试。目的:判断是否可以进行后续的正式测试工作。 新编译的软件版本,确认其基本功能正常。 2、回归测试 修改后重新测试。 错误被修正后或软

    2023年04月13日
    浏览(70)
  • 如何在Arch Linux上安装最新的GNOME Alpha/Beta版本

    导读 这是为那些想在 Arch Linux 上安装下一个主要版本的 GNOME 桌面环境的 alpha 或 beta 开发版的用户提供的快速而又肮脏的教程,仅供测试之用。 每次有新的 GNOME alpha 版本发布,人们都会问我如何在各种 GNU/Linux 发行版上安装。我总是告诉他们,如果没有为特定发行版创建的

    2024年02月16日
    浏览(34)
  • 简谈软件版本周期 | Alpha、Beta、RC、Stable版本之间的区别

    目录 💌 引言 ⭕ 软件版本周期 🛠️ 软件开发期 ⚖️ 软件完成期 💰 商业软件版本 定义好版本号,对于产品的版本发布与持续更新很重要;但是对于版本怎么定义,规则如何确定,却是千差万别。具体应用,可以结合自己目前的实际情况命名。另外,对于商业软件,有的

    2024年02月08日
    浏览(45)
  • sklearn中决策树模块的剪枝参数ccp_alpha如何可视化调整

    决策树作为树模型中最经典的算法,根据训练数据生长并分裂叶子结点,容易过拟合。所以一般来说会考虑生长停止后进行剪枝,把一些不必要的叶子结点去掉(让其父结点作为叶子结点),这样或许对其泛化能力有积极作用。 在scikit-learn的决策树模块里,默认是不剪枝的,

    2024年02月21日
    浏览(44)
  • 深入理解缓存 TLB 原理

    今天分享一篇TLB的好文章,希望大家夯实基本功,让我们一起深入理解计算机系统。 TLB 是 translation lookaside buffer 的简称。首先,我们知道 MMU 的作用是把虚拟地址转换成物理地址。 虚拟地址和物理地址的映射关系存储在页表中,而现在页表又是分级的。64 位系统一般都是

    2024年02月14日
    浏览(48)
  • 深入理解 Flutter 图片加载原理

    作者:京东零售 徐宏伟 来源:京东云开发者社区 随着Flutter稳定版本逐步迭代更新,京东APP内部的Flutter业务也日益增多,Flutter开发为我们提供了高效的开发环境、优秀的跨平台适配、丰富的功能组件及动画、接近原生的交互体验,但随之也带来了一些OOM问题,通过线上监控

    2024年02月12日
    浏览(46)
  • 深入理解网络中断:原理与应用

    🔭 嗨,您好 👋 我是 vnjohn,在互联网企业担任 Java 开发,CSDN 优质创作者 📖 推荐专栏:Spring、MySQL、Nacos、Java,后续其他专栏会持续优化更新迭代 🌲文章所在专栏:网络 I/O 🤔 我当前正在学习微服务领域、云原生领域、消息中间件等架构、原理知识 💬 向我询问任何您想

    2024年02月04日
    浏览(45)
  • 深入理解负载均衡原理及算法

    在互联网早期,网络还不是很发达,上网用户少,流量相对较小,系统架构以单体架构为主。但如今在互联网发达的今天,流量请求动辄百亿、甚至上千亿,单台服务器或者实例已完全不能满足需求,这就有了集群。不论是为了实现高可用还是高性能,都需要用到多台机器来

    2024年02月14日
    浏览(45)
  • 深入理解动态规划的数学原理

    作者:禅与计算机程序设计艺术 动态规划(Dynamic Programming, DP)是计算机科学领域中一个经典的优化模型。它通过解决最优化问题的方式,在一组可能的状态集合中,选取最优子结构,从而找出全局最优解或得到近似解。在很多情况下,动态规划比分治法更有效率,因为它可以

    2024年02月08日
    浏览(58)
  • 深入理解Vue的响应式原理

    用过Vue这个框架的人应该都知道,数据驱动是Vue框架的核心,数据双向绑定是它的一大特色,根据官方的解释,我们可以比较清晰地去知道响应式的简单原理。 当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.definePro

    2023年04月17日
    浏览(27)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包