【Unity学习笔记】A*寻路算法

在这里插入图片描述


文章目录

    • 寻路算法
      • BFS广度优先算法
      • DFS深度优先
      • 贪心算法
    • 引入权重
      • Dijkstra算法
  • A*算法
  • C#实现
    • 步骤
  • Unity中的A*算法
    • A*优化建议


图的知识盘点

pathfinding

作为一名计算机专业的学生,对于图这种数据结构也是烂熟于心了。图是一种包含了多个结点的数据结构,它是结点和路径的集合。可以用邻接表和邻接矩阵来表示一张图。

图的表示可以通过计算得到结点之间的可达和不可达,耗费的代价等等。在许多数学问题上十分有用。通常解决图问题的算法被我们称为寻路算法。

寻路算法

在大学期间,我们就学习过一些基本的寻路算法:

BFS广度优先算法

在这里插入图片描述
广度优先算法的原理是,以起始点为中心,向四周的相邻点(也就是与当前点连通且路长为1的那些节点)依次遍历。当遍历完相邻节点后,再遍历相邻结点的相邻点,依次类推。

在这里插入图片描述

当找到结束点后,只需要从终点开始,反向按着相邻节点的最小遍历序号数寻找,就能找到最短路径了

DFS深度优先

在这里插入图片描述

如果说广度优先是以中心向四周扩散的圆形方式寻找,深度优先就是一条线的一直找下去。深度优先不断的寻找未经过的相邻节点,直到不再有可遍历的相邻节点才会考虑回退到上一个节点并再换一个方向找。

深度优先算法其实更适合在树种的查找,而在图中特别是当一些图的节点是全连通的时候,深度优先就很难用了。

贪心算法

贪心算法适用于路径带有不同权值的时候,遍历时我们优先遍历相邻结点中权值较小者,这样做的好处是如果找到了路径,那么我们的路径一定是部分最短的(相邻结点永远是最短的,虽然不一定是全局最短,但是相对来说不会是最差的)


引入权重

现在我们要为寻路算法引入权重值,每个结点的权重值不一样,权重值由起点和终点的距离决定,权重越小则距离越小。

Dijkstra算法

在这里插入图片描述
Dijkstra算法的原理是先算出起始点到其他点的最短路径,再求解。

例如以上图点1到点3为例,第一轮中找到点1到相邻点的最短路径是v1-v5,cost为5。那么我们就按着这个最短路径走,接着第二轮从点v5开始计算v5到其他点的路径长度。依次类推,按照表格上的路径,在第二轮发现v4达到不了v3,因此我们放弃这条路径,再次回到第二轮,选择除了这条路径以外最短的,也就是v1-v5-v2,按照这条路径走发现是可以到达v3的,此时就是我们的最短路径。

当找不到最短路径时,就要回退到本轮的第二短,本轮第二短达不到就第三短…以此类推。若本轮所有路径都不可达,那就回退到上一轮,选择上一轮中未选择的路径。

当图为网格图,且每个结点之间的移动代价相等时,Dijkstra就等于广度优先


现在让我们引入权值计算公式:默认权值为起点和终点间的距离
a b s ( s t a r t . x − e n d . x ) + a b s ( s t a r t . y − e n d . y ) abs(start.x - end.x) + abs(start.y - end.y) abs(start.xend.x)+abs(start.yend.y)

对比Dijkstra算法和贪心算法,在简单的情况下贪心算法更有优势:

在这里插入图片描述
而一旦出现了复杂的地形,贪心算法就不一定是有效的了:

在这里插入图片描述


A*算法

之前讲了这么多,介绍了一些常见的寻路算法。Dijkstra效果好但是可能造成时间上的浪费,贪心算法速度快,但是最终的结果不一定是最短路径。

因此我们要学习A*算法:

在这里插入图片描述

在A*算法下,找到的路径长度将是最短的,且用时要比Dijkstra算法要小。

A*算法是一种启发式算法,它通过这个函数来计算每个结点的优先级:

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

其中

  • f(n)是节点n的综合优先级。当我们选择下一个要遍历的节点时,我们总会选取综合优先级最高(值最小)的节点。
  • g(n) 是节点n距离起点的代价。
  • h(n)是节点n距离终点的预计代价,这也就是A*算法的启发函数。关于启发函数我们在下面详细讲解。

A*算法在运算过程中,每次从优先队列中选取f(n)值最小(优先级最高)的结点作为下一个待遍历的节点。而不是类似Dijkstra算法,每次遍历相邻结点时尽管下一个结点可能出现权值过大的情况,但是我们不能确保它不是全局最优的,因此我们依然要把它加入到遍历路径中。

而相比下贪心算法虽然局部最优,但也不能确保是全局最优解。

A*算法的高明之处在于,除了实际代价,还有预估代价作为参考,当遍历结点越接近终点,则 g ( n ) 越大, h ( n ) 越小 g(n)越大,h(n)越小 g(n)越大,h(n)越小,因此往往在最短路径上的结点值 f ( n ) f(n) f(n)相差不大。因此我们可以用贪心算法选取优先路径,再用Dijkstra进行遍历。遍历的节点数量也小于Dijkstra算法 。

如果选取的启发函数有问题,那么结果可能更偏向Dijkstra算法。


C#实现

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/qq_60125117/article/details/130233023

转自C# A*算法,总结的太好了,我就直接抄了

A*算法使用一个开放列表(open list)和一个封闭列表(closed list)来维护搜索过程。开放列表存储待扩展的节点,而封闭列表存储已经扩展过的节点,以避免重复扩展节点。

在搜索过程中,我们从开放列表中选择f值最小的节点进行扩展,并将其加入封闭列表中。如果该节点是目标状态,则搜索结束,并返回从起点到目标状态的最短路径。

步骤

  1. 将起点加入开放列表,并将其代价设置为0。
  2. 从开放列表中选择代价 f ( n ) f(n) f(n)值最小的节点进行扩展,即优先选择离目标状态更接近的节点。
  3. 将该节点从开放列表中移除,并加入封闭列表中。
  4. 对该节点的所有相邻节点进行以下操作:
    1. 如果该节点已经在封闭列表中,则忽略该节点
    2. 如果该节点不在开放列表中,则将其加入开放列表,并将其代价和父节点设置为当前节点
    3. 如果该节点已经在开放列表中,则比较当前节点到该节点的代价和已有的代价,选择代价较小的路径,并更新该节点的代价和父节点。
  5. 重复步骤2-4,直到开放列表为空或找到目标状态。
  6. 如果找到目标状态,则从目标状态开始回溯路径,直到回溯到初始状态。
public List<Node> FindPath(Node start, Node end)
{
    // 存储已访问的节点
    var closedSet = new HashSet<Node>();
    // 存储待访问的节点
    var openSet = new Heap<Node>(nodeComparer);
    openSet.Add(start);
 
    // 存储节点到起点的实际代价
    var gScore = new Dictionary<Node, float>();
    gScore[start] = 0;
 
    // 存储节点到终点的估计代价
    var hScore = new Dictionary<Node, float>();
    hScore[start] = HeuristicCostEstimate(start, end);
 
    // 存储每个节点的父节点,用于回溯路径
    var cameFrom = new Dictionary<Node, Node>();
 
    while (openSet.Count > 0)
    {
        // 获取最小估价的节点
        var current = openSet.RemoveFirst();
 
        if (current == end)
        {
            // 找到目标状态,回溯路径
            var path = new List<Node>();
            while (current != start)
            {
                path.Add(current);
                current = cameFrom[current];
            }
            path.Reverse();
            return path;
        }
 
        // 标记当前节点已访问
        closedSet.Add(current);
 
        // 遍历当前节点的相邻节点
        foreach (var neighbor in current.Neighbors)
        {
            if (closedSet.Contains(neighbor))
                continue; // 相邻节点已经访问过了
 
            // 计算当前节点到相邻节点的实际代价
            // 关于代价函数t(n)=g(n)+h(n),其中启发函数h(n)是可以自定义的,只要效果好就行
            var tentativeGScore = gScore[current] + DistanceBetween(current, neighbor);
 
            // 如果相邻节点不在待访问列表中,或者到相邻节点的代价更小
            if (!openSet.Contains(neighbor) || tentativeGScore < gScore[neighbor])
            {
                // 更新相邻节点的父节点和代价
                cameFrom[neighbor] = current;
                gScore[neighbor] = tentativeGScore;
                hScore[neighbor] = HeuristicCostEstimate(neighbor, end);
 
                // 如果相邻节点不在待访问列表中,加入待访问列表
                if (!openSet.Contains(neighbor))
                    openSet.Add(neighbor);
            }
        }
    }
 
    // 找不到目标状态,返回空列表
    return new List<Node>();
}

找到最终结点后,我们只需要从终点开始,一路沿着相邻的OpenList结点中的最小值回溯即可找到寻路的路径。


Unity中的A*算法

Unity中的A算法是一种常用的寻路算法,用于计算在网格或图形地图上找到最短路径。A算法基于图搜索和启发式评估,具有较高的效率和准确性。

其实Unity中要使用A*算法,就是对地图模型的抽象,将其抽象为结点构成的图。我们可以分割网格,或者自定义结点坐标来实现。总之原理是一样的,就是需要抽象出结点的概念:

  1. 创建网格或图形地图:将游戏场景分割为一系列网格或节点,并构建地图数据结构,用于存储每个节点的信息,如位置、连接关系和代价。
  2. 定义节点的启发式评估函数:A*算法使用启发式函数来估计从当前节点到目标节点的代价。这个函数通常基于节点之间的距离或其他因素,用于指导搜索过程。
  3. 实现Open列表和Closed列表:Open列表存储待评估的节点,Closed列表存储已评估过的节点。开始时,将起始节点添加到Open列表中。
  4. 迭代搜索过程:循环执行以下步骤直到找到目标节点或Open列表为空:
    1. 从Open列表中选择代价最小的节点,作为当前节点。
    2. 将当前节点从Open列表中移除,并添加到Closed列表中。
    3. 检查当前节点是否为目标节点,如果是,则路径搜索完成。
    4. 否则,对当前节点的相邻节点进行遍历

A*算法的性能和效果受到地图复杂度、启发式函数的选择以及路径平滑等因素的影响。在实际使用中,可以根据具体需求对算法进行调优和优化


A*优化建议

如果需要对A*算法进行性能优化,可以考虑以下几点:

  1. 使用优先队列:A*算法需要频繁地从开放列表中取出具有最小代价的节点,因此使用优先队列可以提高算法的效率。
  2. 使用启发式算法:启发式算法可以在搜索过程中尽可能地快速地找到目标节点,从而提高算法效率。在实现过程中,需要设计一个好的估价函数,以便尽可能减少搜索的节点数。(好像炼丹啊)
  3. 剪枝:A*算法中有一些无用的搜索节点,可以使用剪枝技术将其剪掉,从而减少搜索时间。
  4. 预处理:预处理是指对搜索的地图进行预处理,以便在搜索过程中可以快速地获取地图信息,从而提高搜索效率。

还有一种针对静态地图的优化方法:使用预处理的路线图(Precomputed Roadmap)。

预处理的路线图是一种离线生成的数据结构,它将地图划分为一些相互连接的区域,并计算出每个区域之间的最短路径。在搜索过程中,可以直接使用预处理的路线图,从而避免了大量的搜索操作。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

巴图自动化Modbus协议转Profinet协议网关模块连智能仪表与PLC通讯

一、现场要求:PLC作为控制器&#xff0c;仪表设备作为执行设备。执行设备可以实时响应PLC传送的指令&#xff0c;并将数据反馈给PLC&#xff0c;从而实现PLC对仪表设备的控制和监控&#xff0c;实现对生产过程的精确控制。 二、解决方案:通过巴图自动化Modbus协议转Profinet协议…

大模型面试题目

1.为什么需要做位置编码 位置编码&#xff08;Positional Encoding&#xff09;在变换器&#xff08;Transformer&#xff09;模型中非常重要&#xff0c;因为变换器架构本身没有内置的顺序信息。变换器使用的是自注意力机制&#xff0c;它能够捕捉输入序列中所有词之间的相关性…

Vscode 保存代码,代码自动格式化

我这里使用的插件是Prettier-Code formatter&#xff1a;自动缩进整理代码的格式&#xff0c;使用方法如下&#xff1a; 先在vscode商店找到插件并安装&#xff1a;安装插件之后&#xff0c;随便找到一个项目文件&#xff0c;右键选择格式化文档&#xff1a;选中我们安装的插件…

LVS-负载均衡

目录 一、概念 二、LVS工作原理 1. ipvs/ipvsadm 2.名词&#xff1a; 三、常用命令 四、工作模式 1.NAT地址转换模式 &#xff08;1&#xff09;工作流程 &#xff08;2&#xff09;特点 &#xff08;3&#xff09;实验过程 a.环境准备&#xff1a; b.修改测试机的…

Wing FTP Server

文章目录 1.Wing FTP Server简介1.1主要特点1.2使用教程 2.高级用法2.1Lua脚本,案例1 1.Wing FTP Server简介 Wing FTP Server&#xff0c;是一个专业的跨平台FTP服务器端&#xff0c;它拥有不错的速度、可靠性和一个友好的配置界面。它除了能提供FTP的基本服务功能以外&#…

android应用的持续构建CI(一)-- 总体设计

一、背景 接下里我希望通过一系列的文章&#xff0c;把android应用的构建梳理一遍&#xff0c;从总体设计到逐个环节的实现。 总体设计jenkins集成手动签名依赖环境应用管理 二、构建流程图 三、技术组件 jenkinsjdkgradle360加固 既然是android应用的持续构建&#xff0c…

科普文:Linux服务器性能调优之CPU调度策略和可调参数

概叙 进程 进程是操作系统虚拟出来的概念&#xff0c;用来组织计算机中的任务。计算机的核心是CPU&#xff0c;它承担了所有的计算任务&#xff1b;而操作系统是计算机的管理者&#xff0c;它负责任务的调度、资源的分配和管理&#xff0c;统领整个计算机硬件&#xff1b;应用…

5百多本分章节古籍内容大全ACCESS\EXCEL数据库

很多明清小说现在越来越不容易查看其内容&#xff0c;虽然之前搞到过一份《3万8千多古代文学大全ACCESS数据库》&#xff0c;但简体中文总让我感觉有删减、非原版的印象&#xff0c;今天正好遇到一个好的古籍网站&#xff0c;繁体字繁体文&#xff0c;感觉非常不错&#xff0c;…

微信小程序遮罩层显示

效果展示&#xff1a; wxml页面&#xff1a; <view classmodal-mask wx:if{{showModal}}><view class"modal-container"><view classmodal-content></view><view classmodal-footer bindtap"closeImage">//这个/images/ind…

电脑录音怎么录?简单四个方法轻松搞定!

在电脑上录制音频是一项非常实用的技能&#xff0c;适合多种场合的需求。例如&#xff0c;你可能需要录制自己的声音&#xff0c;用于录音广播、演示或视频制作&#xff1b;也可能需要录制电脑中的声音&#xff0c;如音乐、游戏音效或在线直播&#xff1b;或者需要捕捉浏览器中…

快排的实现

引言 作为c语言库函数的一种&#xff0c;快排在排序中的地位毋庸置疑. 而更加具体的实现如图&#xff1a; 快排的实现&#xff08;递归实现&#xff09; 原理 单趟:先假定第一个数设为key,如果左边指针的值比key大&#xff0c;且右边指针的值比key小&#xff0c;则将其交换.…

亚马逊云服务器的价格真的那么贵吗?一年要花多少钱?

亚马逊Web服务&#xff08;AWS&#xff09;作为全球领先的云计算平台&#xff0c;其定价策略常常引起用户的关注。很多人可能会问&#xff1a;"AWS真的那么贵吗&#xff1f;"实际上&#xff0c;这个问题的答案并不是简单的"是"或"否"&#xff0c…

JavaScript技术的小饰品销售管理系统-计算机毕业设计源码21597

摘 要 在当今的数字化时代&#xff0c;电子商务已经成为了商业领域中不可或缺的一部分。随着消费者对于购物体验的要求越来越高&#xff0c;一个高效、便捷、用户友好的小饰品销售管理系统显得尤为重要。 本系统旨在利用 JavaScript 技术&#xff0c;设计并实现一个功能强大的小…

黑马点评DAY5|商户查询缓存

商户查询缓存 缓存的定义 缓存就是数据交换的缓冲区&#xff08;Cache&#xff09;&#xff0c;是存储数据的临时地方&#xff0c;一般读写性能较高。 比如计算机的CPU计算速度非常快&#xff0c;但是需要先从内存中读取数据再放入CPU的寄存器中进行运算&#xff0c;这样会限…

ForkJoin框架与工作窃取算法详解

文章目录 一、ForkJoin框架概述1_核心概念2_主要类和方法1_ForkJoinPool2_ForkJoinTask 二、启用异步模式与否的区别三、ForkJoinPool的三种任务提交方式四、执行逻辑及使用示例1_示例&#xff1a;并行计算数组元素和2_forkJoinPool.submit3_ForkJoinTask<?>中任务的执行…

软件研发标准化流程文件

为了规范化系统开发流程&#xff0c;我们精心制定了一套详尽的规范文档。该文档旨在通过标准化、系统化的方法来显著提升开发效率与项目质量。流程始于明确需求阶段&#xff0c;通过深入细致的设计规划来确保解决方案既可行又具有前瞻性。随后&#xff0c;我们进入高效的编码实…

ClickHouse概述

ClickHouse概述 文章目录 ClickHouse概述ClickHouse是什么ClickHouse快的理由什么是OLAPClickHouse的特点列式存储DBMS 的功能多样化引擎高吞吐写入能力数据分区与线程级并行 ClickHouse的应用合适场景不适合场景 ClickHouse是什么 ClickHouse 是俄罗斯的 Yandex 于 2016 年开…

Appium自动化测试框架3

滑动与拖拽 swipe 滑动时间的长短会影响最后的结果的 是有一定误差的 from appium import webdriver import time # 启动一个字典 包装相应的启动参数 desired_caps dict() # 平台的名字&#xff0c;安卓还是IOS 大小写无所谓 desired_caps[platformName] Android # 平台的…

【电源专题】DC-DC电路设计为什么一般只考虑电感DCR而不考虑Q值呢?

什么是电感器(线圈)的Q值&#xff1f; Q值是表示电感器质量的参数。Q是Quality Factor&#xff08;质量系数&#xff09;的简称。线圈会顺利流过直流电流&#xff0c;但会对交流电流产生电阻。这称为感抗&#xff0c;交流频率越高则越大。 此外&#xff0c;绕组虽是导体…

JAVA每日作业day7.4

ok了家人们今天学习了Date类和simpleDateformat类&#xff0c;话不多说我们一起看看吧 一.Date类 类 java.util.Date 表示特定的瞬间 ( 日期和时间 ) &#xff0c;精确到毫秒。 1.2 Date类的构造方法 public Date(): 用来创建当前系统时间对应的日期对象。 public Date(long …