A_搜索(A Star)算法

news2024/11/17 19:48:54

A*搜索(A Star)

不同于盲目搜索,A算法是一种启发式算法(Heuristic Algorithm)
上文提到,盲目搜索对于所有要搜索的状态结点都是一视同仁的,因此在每次搜索一个状态时,盲目搜索并不会考虑这个状态到底是有利于趋向目标的,还是偏离目标的。
而启发式搜索的启发二字,看起来是不是感觉这个算法就变得聪明一点了呢?正是这样,启发式搜索对于待搜索的状态会进行不同的优劣判断,这个判断的结果将会对算法搜索顺序起到一种启发作用,越优秀的状态将会得到越高的搜索优先级。
我们把对于状态优劣判断的方法称为
启发函数*,通过给它评定一个搜索代价来量化启发值。
启发函数应针对不同的使用场景来设计,那么在拼图的游戏中,如何评定某个状态的优劣性呢?粗略的评估方法有两种:

  1. 可以想到,某个状态它的方块位置放对的越多,说明它能复原目标的希望就越大,这个状态就越优秀,优先选择它就能减少无效的搜索,经过它而推演到目标的代价就会小。所以可求出某个状态所有方块的错位数量来作为评估值,错位越少,状态越优秀。
  2. 假如让拼图上的每个方块都可以穿过邻近方块,无阻碍地移动到目标位置,那么每个不在正确位置上的方块它距离正确位置都会存在一个移动距离,这个非直线的距离即为曼哈顿距离(Manhattan Distance),我们把每个方块距离其正确位置的曼哈顿距离相加起来,所求的和可以作为搜索代价的值,值越小则可认为状态越优秀。

其实上述两种评定方法都只是对当前状态距离目标状态的代价评估,我们还忽略了一点,就是这个状态距离搜索开始的状态是否已经非常远了,亦即状态结点的深度值。
在拼图游戏中,我们进行的是路径搜索,假如搜索出来的一条移动路径其需要的步数非常多,即使最终能够把拼图复原,那也不是我们希望的路径。所以,路径搜索存在一个最优解的问题,搜索出来的路径所需要移动的步数越少,就越优。
A*算法对某个状态结点的评估,应综合考虑这个结点距离开始结点的代价与距离目标结点的代价。总估价公式可以表示为:

f(n) = g(n) + h(n)

n 表示某个结点,f(n) 表示对某个结点进行评价,值等于这个结点距离开始结点的已知价 g(n) 加上距离目标结点的估算价 h(n)
为什么说 g(n) 的值是确定已知的呢?在每次生成子状态结点时,子状态的 g 值应在它父状态的基础上 +1,以此表示距离开始状态增加了一步,即深度加深了。所以每一个状态的 g 值并不需要估算,是实实在在确定的值。
影响算法效率的关键点在于 h(n) 的计算,采用不同的方法来计算 h 值将会让算法产生巨大的差异。

  • 当增大 h 值的权重,即让 h 值远超 g 值时,算法偏向于快速寻找到目标状态,而忽略路径长度,这样搜索出来的结果就很难保证是最优解了,意味着可能会多绕一些弯路,通往目标状态的步数会比较多。
  • 当减小 h 值的权重,降低启发信息量,算法将偏向于注重已搜深度,当 h(n) 恒为 0 时,A*算法其实已退化为广度优先搜索了。(这是为照应上文的方便说法。严谨的说法应是退化为 Dijkstra 算法,在本游戏中,广搜可等同为 Dijkstra 算法,关于 Dijkstra 这里不作深入展开。)

以下是拼图状态结点 PuzzleStatus 的估价方法,在实际测试中,使用方块错位数量来作估价的效果不太明显,所以这里只使用曼哈顿距离来作为 **h(n)** 估价,已能达到不错的算法效率。

/// 估算从当前状态到目标状态的代价
- (NSInteger)estimateToTargetStatus:(id<JXPathSearcherStatus>)targetStatus {
    PuzzleStatus *target = (PuzzleStatus *)targetStatus;
    
    // 计算每一个方块距离它正确位置的距离
    // 曼哈顿距离
    NSInteger manhattanDistance = 0;
    for (NSInteger index = 0; index < self.pieceArray.count; ++ index) {
        // 略过空格
        if (index == self.emptyIndex) {
            continue;
        }
        
        PuzzlePiece *currentPiece = self.pieceArray[index];
        PuzzlePiece *targetPiece = target.pieceArray[index];
        
        manhattanDistance +=
        ABS([self rowOfIndex:currentPiece.ID] - [target rowOfIndex:targetPiece.ID]) +
        ABS([self colOfIndex:currentPiece.ID] - [target colOfIndex:targetPiece.ID]);
    }
    
    // 增大权重
    return 5 * manhattanDistance;
}

状态估价由状态类自己负责,A算法只询问状态的估价结果,并进行 f(n) = g(n) + h(b) 操作,确保每一次搜索,都是待搜空间里代价最小的状态,即 f 值最小的状态。
那么问题来了,在给每个状态都计算并赋予上 **f** 值后,如何做到每一次只取 **f** 值最小的那个?
前文已讲到,所有扩展出来的新状态都会放入开放队列中的,如果 A
算法也像广搜那样只放在队列尾,然后每次只取队首元素来搜索的话,那么 f 值完全没有起到作用。
事实上,因为每个状态都有 f 值的存在,它们已经有了优劣高下之分,队列在存取它们的时候,应当按其 f 值而有选择地进行入列出列,这时候需要用到优先队列(Priority Queue),它能够每次出列优先级最高的元素。
以下是 A*搜索算法的代码实现:

- (NSMutableArray *)search {
    if (!self.startStatus || !self.targetStatus || !self.equalComparator) {
        return nil;
    }
    NSMutableArray *path = [NSMutableArray array];
    [(id<JXAStarSearcherStatus>)[self startStatus] setGValue:0];
    
    // 关闭堆,存放已搜索过的状态
    NSMutableDictionary *close = [NSMutableDictionary dictionary];
    // 开放队列,存放由已搜索过的状态所扩展出来的未搜索状态
    // 使用优先队列
    JXPriorityQueue *open = [JXPriorityQueue queueWithComparator:^NSComparisonResult(id<JXAStarSearcherStatus> obj1, id<JXAStarSearcherStatus> obj2) {
        if ([obj1 fValue] == [obj2 fValue]) {
            return NSOrderedSame;
        }
        // f值越小,优先级越高
        return [obj1 fValue] < [obj2 fValue] ? NSOrderedDescending : NSOrderedAscending;
    }];
    
    [open enQueue:self.startStatus];
    
    while (open.count > 0) {
        // 出列
        id status = [open deQueue];
        
        // 排除已经搜索过的状态
        NSString *statusIdentifier = [status statusIdentifier];
        if (close[statusIdentifier]) {
            continue;
        }
        close[statusIdentifier] = status;
        
        // 如果找到目标状态
        if (self.equalComparator(self.targetStatus, status)) {
            path = [self constructPathWithStatus:status isLast:YES];
            break;
        }
        
        // 否则,扩展出子状态
        NSMutableArray *childStatus = [status childStatus];
        // 对各个子状进行代价估算
        [childStatus enumerateObjectsUsingBlock:^(id<JXAStarSearcherStatus>  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            // 子状态的实际代价比本状态大1
            [obj setGValue:[status gValue] + 1];
            // 估算到目标状态的代价
            [obj setHValue:[obj estimateToTargetStatus:self.targetStatus]];
            // 总价=已知代价+未知估算代价
            [obj setFValue:[obj gValue] + [obj hValue]];
            
            // 入列
            [open enQueue:obj];
        }];
    }
    NSLog(@"总共搜索: %@", @(close.count));
    return path;
}

可以看到,代码基本是以广搜为模块,加入了 f(n) = g(n) + h(b) 的操作,并且使用了优先队列作为开放表,这样改进后,算法的效率是不可同日而语。
9687b20e5c69b21383489191f5510e81.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1115685.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

STM32使用WWDG窗口看门狗

1 WWDG 介绍 1.1 WWDG 简介 窗口看门狗 WWDG 其实和独立看门狗类似&#xff0c;它是一个 7 位递减计数器不断的往下递减计数&#xff0c; 当减到一个固定值 0X40 时还不喂狗的话&#xff0c;产生一个 MCU 复位&#xff0c;这个值叫窗口的下限&#xff0c;是固定的值&#xf…

Android 开发技巧:音乐播放器的后台处理【Service、Handler、MediaPlayer】

给定部分完成的MusicPlayer项目&#xff0c;实现其中未完成的service部分&#xff1a; 1、创建MusicService类&#xff0c;通过service组件实现后台播放音乐的功能&#xff1b; 2、在MainActivity中通过ServiceConnection连接MusicService&#xff0c;实现对音乐播放的控制&…

Android端自定义铃声

随着移动应用竞争进入红海时代&#xff0c;如何在APP推送中别出心裁显得尤为重要。例如对自己的APP推送赋予独特的推送铃声&#xff0c;能够给用户更加理想的使用体验。 1、个性化提醒铃声有助于当收到特定类型的消息时&#xff0c;用户能够立刻识别出来。 2、不同的推送铃声…

xxl-job任务调度2.0.2升级到2.3.0版本,执行器改造过程中经验总结

xxl-job任务调度2.0.2升级到2.3.0版本 一、背景二、开始改造1、修改pom.xml2、修改Handler3、启动服务并验证4、解决异常5、再次启动服务并验证 一、背景 现在要对一批老项目中的执行器进行升级&#xff0c;原来老项目中的执行器&#xff0c;依赖的任务调度中心xxl-job版本是2…

金和OA SQL注入漏洞

一、漏洞描述 金和OA协同办公管理系统C6软件&#xff0c;为北京金和网络股份有限公司 开发系统&#xff0c;贴合企事业单位的实际需求&#xff0c;实行通用化、标准化、智能化、人性化的产品设计&#xff0c;充分体现企事业单位规范管理、提高办公效率的核心思想&#xff0c;为…

Go包介绍与初始化:搞清Go程序的执行次序

Go包介绍与初始化&#xff1a;搞清Go程序的执行次序 文章目录 Go包介绍与初始化&#xff1a;搞清Go程序的执行次序一、main.main 函数&#xff1a;Go 应用的入口函数1.1 main.main 函数1.2 main.main 函数特点 二、包介绍2.1 包介绍与声明2.2 非 main包的 main 函数2.3 包的命名…

剑指Offer || 050.路径总和|||

题目 给定一个二叉树的根节点 root &#xff0c;和一个整数 targetSum &#xff0c;求该二叉树里节点值之和等于 targetSum 的 路径 的数目。 路径 不需要从根节点开始&#xff0c;也不需要在叶子节点结束&#xff0c;但是路径方向必须是向下的&#xff08;只能从父节点到子节…

DETR原理与代码超详细解读

文章目录 前言一、DETR论文原理1、DETR整体介绍2、DETR论文贡献3、DETR模型框架4、DETR基于二分图匹配的LOSS 二、DETR环境安装1、安装基础环境2、pycocotools安装3、其它环境安装4、环境验证5、训练与推理效果显示 三、数据准备1、coco 数据格式2、修改数据 四、DETR加载数据代…

Git的安装

前置 知道自己电脑上跑的是什么系统 查看电脑位数 省事的一种办法 Windows 在cmd中输入如下命令 wmic os get osarchitecture看命令结果即可 省事的一种办法 Linux 直接在终端中输入如下命令 uname -m若结果是x86_64就是64位的&#xff0c;反之32位 图形化的办法 Wind…

Peter算法小课堂—正整数拆分

大家可能会想&#xff1a;正整数拆分谁不会啊&#xff0c;2年级就会了&#xff0c;为啥要学啊 例题 正整数拆分有好几种&#xff0c;这里我们列举两种讲。 关系 我们看着第一幅图&#xff0c;头向左转90&#xff0c;记住你看到的图&#xff0c;再来看第二幅图&#xff0c;你…

lnmp架构部署Discuz论坛并配置重定向转发

lnmp架构部署Discuz论坛并配置重定向转发 文章目录 lnmp架构部署Discuz论坛并配置重定向转发环境说明部署Discuz论坛系统下载Discuz论坛系统代码包&#xff0c;官网地址如下&#xff1a;部署Discuz论坛系统步骤&#xff1a;解压安装Discuz源码包配置虚拟主机进入Discuz安装界面…

pytest利用request fixture实现个性化测试需求详解

这篇文章主要为大家详细介绍了pytest如何利用request fixture实现个性化测试需求,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起了解一下− 前言 在深入理解 pytest-repeat 插件的工作原理这篇文章中&#xff0c;我们看到pytest_repeat源码中有这样一段 import pyt…

YOLOv5算法改进(14)— 如何去更换主干网络(3)(包括代码+添加步骤+网络结构图)

前言:Hello大家好,我是小哥谈。为了给后面YOLOv5算法的进阶改进奠定基础,本篇文章就继续通过案例的方式给大家讲解如何在YOLOv5算法中更换主干网络,本篇文章的特色就是比较浅显易懂,附加了很多的网络结构图,通过结构图的形式向大家娓娓道来,希望大家学习之后能够有所收获…

msvcr120.dll缺失怎么修复,快速修复msvcr120.dll丢失的三个有效方法

随着计算机技术的不断发展&#xff0c;我们在使用软件或游戏时经常会遇到各种错误提示&#xff0c;其中找不到msvcr120.dll就是一种常见的错误。那么&#xff0c;msvcr120.dll是什么&#xff1f;它的作用是什么&#xff1f;如何修复这一错误呢&#xff1f;本文将为您详细介绍几…

Python学习基础笔记七十九——Socket编程2

应用消息格式&#xff1a; 为什么要定义消息格式&#xff1f; 我们发送的消息就是要传递的内容&#xff0c;比如字符串。 我们在企业中开发的程序通讯&#xff0c;消息往往是有格式定义的。消息格式的定义可以归入OSI网络模型的表示层。 比如&#xff1a;定义的消息包括消息…

电子技术基础(三)__第7章 时序逻辑电路_第7篇之解题方法与步骤

我们必须牢记这样一句&#xff1a; 先列出输入逻辑式&#xff0c;即驱动方程&#xff0c; 然后将驱动方程代入触发器的特征方程&#xff0c; 得到状态方程(也称 次态方程)&#xff0c; 最后写出输出方程。 二 看例题 2022.4 真题 第38题 分析: 此题乍一看&#xff0c;可能有点…

jvm 各个版本支持的参数 容器化部署

知道一些 jvm 调优参数&#xff0c;但是没有找到官网对应的文档&#xff0c;在网上的一些文章偶然发现&#xff0c;记录一下。 https://docs.oracle.com/en/java/javase/ 包含各个版本 jdk 8 分为 windows 和 unix 系统 https://docs.oracle.com/javase/8/docs/technotes/too…

Volatile 可以保证什么特性?有什么作用?

Volatile 可以保证什么特性&#xff1f;有什么作用&#xff1f; 可以保证可见性&#xff0c;有序性&#xff0c;禁止指令重排序。但是不能保证原子性。线程上下文切换的时候&#xff0c;还是有可能出现线程安全问题。 cpu 都是把数据从内存拉取到自己的缓存中进行运算&#xff…

在Espressif-IDE中使用Wokwi仿真ESP32

陈拓 2023/10/17-2023/10/19 1. 概述 在Espressif-IDE v2.9.0版本之后可直接在IDE中使用Wokwi模拟器。 1.1 什么是 Wokwi 模拟器&#xff1f; Wokwi 是一款在线电子模拟器&#xff0c;支持模拟各种开发板、元器件和传感器&#xff0c;例如乐鑫产品 ESP32。 Wokwi 提供基于浏…

Vue3.0的设计目标是什么?做了哪些优化

一、设计目标 不以解决实际业务痛点的更新都是耍流氓&#xff0c;下面我们来列举一下Vue3之前我们或许会面临的问题 随着功能的增长&#xff0c;复杂组件的代码变得越来越难以维护缺少一种比较「干净」的在多个组件之间提取和复用逻辑的机制类型推断不够友好bundle的时间太久…