算法修养--A*寻路算法

这篇具有很好参考价值的文章主要介绍了算法修养--A*寻路算法。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

A*寻路算法

广度优先算法

广度优先算法搜索以广度做为优先级进行搜索。

从起点开始,首先遍历起点周围邻近的点,然后再遍历已经遍历过的点邻近的点,逐步的向外扩散,直到找到终点。

这种算法就像洪水(Flood fill)一样向外扩张。直至洪水将整张地图都漫延。在访问节点时候,每个点需要记录到达该点的前一个点的位置(父节点),访问到终点时候,便可以从终点沿着父节点一路走回到起点,从而找出路径。(注意:这也是A*算法的一部分)

这种洪水蔓延式寻找路径的方式较为野蛮粗暴,仅仅依据广度来找路径难以找到最短的路径

算法修养--A*寻路算法

Dijkstra算法

在实际寻路场景中,要考虑“移动成本”。不同的路径有不同的成本,例如,穿过平原或沙漠可能需要 1 个移动点,但穿过森林或丘陵可能需要 5 个移动点。玩家在水中行走的成本是在草地上行走的 10 倍。为此我们需要Dijkstra算法。

在Dijkstra算法中,需要计算每一个节点距离起点移动的总移动代价,同时,还需要一个优先队列结构,对于所有待遍历的节点,放入优先队列中,优先队列会按照代价进行排序。(这也是在下面要介绍的A*算法实现案例中待优化的点!)

在算法运行过程中,每次都从优先队列中选出移动成本最小的作为下一个要遍历检查的节点,直到访问到达终点为止。

算法修养--A*寻路算法

两种算法的对比:

考虑这样一种场景,在一些情况下,图形中相邻节点之间的移动代价并不相等。例如,游戏中的一幅图,既有平地也有山脉,那么游戏中的角色在平地和山脉中移动的速度通常是不相等的。

在Dijkstra算法中,需要计算每一个节点距离起点的总移动代价。同时,还需要一个优先队列结构。对于所有待遍历的节点,放入优先队列中会按照代价进行排序。

在算法运行的过程中,每次都从优先队列中选出代价最小的作为下一个遍历的节点。直到到达终点为止。

下面对比了不考虑节点移动代价差异的广度优先搜索与考虑移动代价的Dijkstra算法的运算结果:

广度优先算法: Dijkstra算法:

算法修养--A*寻路算法

当然,如果不考虑移动代价的因素,在同一个网格图中Dijkstra算法将和广度优先算法一样。

启发式搜索?

通过广度优先搜索和 Dijkstra 算法,边界向各个方向扩展。如果试图找到通往所有位置或许多位置的路径,这是一个合理的选择。

如果仅仅是找到到达一个位置的路径,我们应当控制洪水的流向,时期朝向目标位置流动。而控制方向的条件判断便可以通过启发式搜索来完成。

而所谓启发式搜索函数便是用来计算当前点到达目标点的距离(步数),预测到达目标点的距离。

启发函数来计算到目标点距离方式有多种选择:

  • 曼哈顿距离

    如果图形中只允许朝上下左右四个方向移动,则启发函数可以使用曼哈顿距离,它的计算方法如下图所示:

    曼哈顿距离=abs(node.x-target.x)+abs(node.y-target.y)

  • 对角移动

    如果图形中允许斜着朝邻近的节点移动,则启发函数可以使用对角距离。

  • 欧几里得距离

    如果图形中允许朝任意方向移动,则可以使用欧几里得距离。

    欧几里得距离2=(node.x-target.x)2+(node.y-target.y)2

采用启发函数控制广度优先搜索的方式为(贪婪)最佳优先搜索。

最佳优先搜索(Best First)

最佳优先搜索的原理也简单,与Dijkstra算法类似(广度优先+移动成本),也使用一个优先队列存储启发函数值(该点与目标点的距离),每次选取与目标点最近的点(邻居)来访问,直至访问到终点。

可以理解算法是广度优先算法+选择最小目标点距离参数的结果。

算法修养--A*寻路算法

同样,由于没有考虑移动成本,在移动成本不同的地图中也不能考虑到森林、沼泽等移动速度不同的场景造成的移动成本的增加。不能保证找到的路径是最短的路径。

那就结合在一起呗!

A*算法=广度优先算法+考虑“移动成本”+考虑“与目标点的距离”;

既可以保证聪明地选择好走的路线避开难走的路线或者障碍物,也可以集中管控搜索方向向着目标点中的位置搜寻。

下面是对三个算法的详细对比:

A*算法集合了前两个算法的优势,可以保证在两点之间找到最佳的路线。

算法修养--A*寻路算法

A*寻路算法

基本原理:

A*算法使用如下所示的函数来计算每个节点的优先级:

\[f(n)=g(n)+h(n) \]

f(n)是节点n的综合优先级。

g(n)表示节点n距离起点的代价(距离或步数),即:从起点出发到达当前点所走的步数;

h(n)则表示节点n距离终点(目标点)的预估代价,即:从当前点达到终点需要走的步数,这是A*算法的启发函数,相当于提前预测要走到目标点还需要的步数。由于是预测,按照最短的路径(步数)来表示,所以实际上到达终点的路程要等于或大于预测的数值。

寻路算法的启发函数控制:(理解两个参数对搜寻的影响)

  • 在极端情况下,当启发函数h(n)始终为0,则将由g(n)决定节点的优先级,此时算法就退化成了Dijkstra算法。
  • 如果h(n)始终小于等于节点n到终点的代价,则A*算法保证一定能够找到最短路径。但是当h(n)的值越小,算法将遍历越多的节点,也就导致算法越慢。
  • 如果h(n)完全等于节点n到终点的代价,则A*算法将找到最佳路径,并且速度很快。可惜的是,并非所有场景下都能做到这一点。因为在没有达到终点之前,我们很难确切算出距离终点还有多远。
  • 如果h(n)的值比节点n到终点的代价要大,则A*算法不能保证找到最短路径,不过此时会很快。
  • 在另外一个极端情况下,如果h()n相较于g(n)大很多,则此时只有h(n)产生效果,这也就变成了最佳优先搜索。

在寻路过程中,每次依据周边节点的f(n)数值来选择,每次选择最小的f(n);

算法修养--A*寻路算法

有障碍物的情况下的寻路(黑色方块为障碍物)

算法修养--A*寻路算法

代码实现:

//节点类
public class NodeBase{
    public NodeBase Connection{get;private set;}//存储自己的相连接路径中的节点
    public float G{get;private set;}
    public float H{get;private set;}
    public float F=>G+H;
    
    public void SetConnetion(NodeBase nodeBase)=>Connection=nodeBase;
    public void SetG(float g)=>G=g;
    public void SetH(float h)=>H=h;
}
//核心算法
public static class Pathfinding {
    /// <summary>
    /// A*算法核心
    /// </summary>
    /// <param name="startNode">开始点</param>
    /// <param name="targetNode">目标点</param>
    /// <returns>最短路径</returns>
    /// <exception cref="Exception"></exception>
    public static List<NodeBase> FindPath(NodeBase startNode, NodeBase targetNode) {
        var toSearch = new List<NodeBase>() { startNode };//要处理搜寻的节点(未访问过)
        var processed = new List<NodeBase>();//存储访问过的节点

        while (toSearch.Any()) {//判断是否有要搜寻的元素节点
            //fixit 待优化的点 可以使用一个堆排序思想 
            var current = toSearch[0];
            foreach (var t in toSearch) 
                if (t.F < current.F || t.F == current.F && t.H < current.H) current = t;

            processed.Add(current);
            toSearch.Remove(current);//搜寻过后就移除

            current.SetColor(ClosedColor);

            //终点检查
            if (current == targetNode) {
                var currentPathTile = targetNode;
                var path = new List<NodeBase>();//保存路径
                var count = 100;
                //从终点到出发点的节点路径寻找
                while (currentPathTile != startNode) {
                    path.Add(currentPathTile);
                    currentPathTile = currentPathTile.Connection;
                    count--;//这里根据地图的大小设置一个路径长度极限(可以忽略)
                    if (count < 0) throw new Exception();
                    Debug.Log("sdfsdf");
                }
                foreach (var tile in path) tile.SetColor(PathColor);
                startNode.SetColor(PathColor);
                Debug.Log(path.Count);
                return path;//返回路径
            }
            //关心一下自己的邻居--可以到达的邻居,并且还未访问过的(非障碍物)
            foreach (var neighbor in current.Neighbors.Where(t => t.Walkable && !processed.Contains(t))) {
                var inSearch = toSearch.Contains(neighbor);//获取对邻居的访问状况
                var costToNeighbor = current.G + current.GetDistance(neighbor);
                //更新一下邻居的G值
                //对于已经访问过的邻居 如果从另一个方向过来想再次访问,那么G值应当比之前访问的要小(只能是最近路程访问)
                if (!inSearch || costToNeighbor < neighbor.G) {
                    neighbor.SetG(costToNeighbor);
                    neighbor.SetConnection(current);//记录与之连接的节点(算作路径中)
                    //如果是第一次访问 还需要进行H值的计算
                    if (!inSearch) {
                        neighbor.SetH(neighbor.GetDistance(targetNode));//计算启发函数值
                        toSearch.Add(neighbor);//将邻居添加到要访问的列表中
                    }
                }
            }
        }
        return null;
    }
}

参考文章:https://www.redblobgames.com/pathfinding/a-star/introduction.html

参考视频:https://www.youtube.com/watch?v=i0x5fj4PqP4

参考项目:https://github.com/zhm-real/PathPlanning文章来源地址https://www.toymoban.com/news/detail-711591.html

到了这里,关于算法修养--A*寻路算法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • Python 算法基础篇:深度优先搜索( DFS )和广度优先搜索( BFS )

    深度优先搜索( DFS )和广度优先搜索( BFS )是两种常用的图遍历算法,用于在图中搜索目标节点或遍历图的所有节点。本篇博客将介绍 DFS 和 BFS 算法的基本概念,并通过实例代码演示它们的应用。 😃😄 ❤️ ❤️ ❤️ 深度优先搜索( DFS )是一种用于遍历或搜索图或树

    2024年02月07日
    浏览(52)
  • 深度优先搜索(DFS)和广度优先搜索(BFS)两种算法c++

    深度优先搜索(DFS)和广度优先搜索(BFS)是一种用于遍历或搜索树图的一种算法,在这个过程中保证图或数的每个结点被访问且仅被访问一次,再按照每个结点访问的顺序不同分为深搜和广搜。 本文只讨论这两种算法在搜索方面的应用! 深度优先搜索 ( Depth-First-Search,DFS )它 沿

    2024年02月13日
    浏览(33)
  • 数据结构学习笔记——图的遍历算法(深度优先搜索和广度优先搜索)

    图的遍历指从图中某一顶点出发(任意一个顶点都可以作为访问的起始顶点),按照某种遍历方法,对图中所有的顶点访问一次且只访问一次。图与树不一样,其中一个顶点可能与多个顶点相连,所以需记录已访问过的顶点,当访问一个顶点后,考虑如何选取下一个要访问的

    2024年02月05日
    浏览(39)
  • 图的遍历(搜索)算法(深度优先算法DFS和广度优先算法BFS)

    从图的某个顶点出发访问遍图中所有顶点,且每个顶点仅被访问一次。(连通图与非连通图) 1、访问指定的起始顶点; 2、若当前访问的顶点的邻接顶点有未被访问的,则任选一个访问之;反之,退回到最近访问过的顶点;直到与起始顶点相通的全部顶点都访问完毕; 3、若

    2024年01月17日
    浏览(36)
  • Python 图算法,图最短路径,图广度优先搜索,图深度优先搜索,图排序

    一、图数据库相关算法 图数据库是一种专门用来存储和处理图数据的数据库系统。它使用图结构来表示数据之间的关联关系,以及节点和边之间的属性信息。以下是一些常用的图数据库算法: 1. 最短路径算法:最短路径算法用于计算图中两个节点之间的最短路径,例如Dijk

    2024年02月15日
    浏览(24)
  • 【Python搜索算法】广度优先搜索(BFS)算法原理详解与应用,示例+代码

    目录 1 广度优先搜索     2 应用示例 2.1 迷宫路径搜索 2.2 社交网络中的关系度排序 2.3 查找连通区域         广度优先搜索(Breadth-First Search,BFS)是一种图遍历算法,用于系统地遍历或搜索图(或树)中的所有节点。BFS的核心思想是从起始节点开始,首先访问其所有相

    2024年02月08日
    浏览(37)
  • 算法修养--A*寻路算法

    广度优先算法搜索以广度做为优先级进行搜索。 从起点开始,首先遍历起点周围邻近的点,然后再遍历已经遍历过的点邻近的点,逐步的向外扩散,直到找到终点。 这种算法就像洪水(Flood fill)一样向外扩张。直至洪水将整张地图都漫延。在访问节点时候,每个点需要记录

    2024年02月08日
    浏览(22)
  • 图论算法|深度优先搜索理论基础|797.所有可能的路径|广度优先搜索BFS理论基础|200. 岛屿数量

    dfs是可一个方向去搜,不到黄河不回头,直到遇到绝境了,搜不下去了,在换方向(换方向的过程就涉及到了回溯)。 递归和回溯是相辅相成的 https://leetcode.cn/problems/all-paths-from-source-to-target/ 有向无环图(DAG): 有环无向图是指在图中存在至少一个环(Cycle)的无向图。环是

    2024年02月15日
    浏览(46)
  • 数据结构与算法基础-学习-24-图的遍历之DFS(深度优先搜索)和BFS(广度优先搜索)

    目录 一、遍历定义 二、遍历实质 三、DFS 四、BFS 五、宏定义 六、自定义类型 七、函数实现 1、DFS(邻接矩阵实现) 2、DFS(邻接表实现) 3、BFS(邻接矩阵实现) 4、BFS(邻接表实现) 5、打印邻接矩阵遍历顺序  6、打印邻接表遍历顺序 八、遍历算法效率分析 1、DFS 2、BFS 九

    2024年02月03日
    浏览(59)
  • 广度优先搜索(BFS)算法求解单源最短路径问题

      单源最短路径问题是图论中一类重要且常见的应用问题。在这个问题中,我们需要找到从一个给定源节点到其他节点的最短路径。广度优先搜索(BFS)算法是一种常用且有效的求解这一问题的算法。   本篇博客将重点讨论单源最短路径问题及其实际应用,同时简要介绍

    2024年02月13日
    浏览(33)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包