C#实现A*算法

理解A*寻路算法具体过程

这两天研究了下 A* 寻路算法, 主要学习了这篇文章, 但这篇翻译得不是很好, 我花了很久才看明白文章中的各种指代. 特写此篇博客用来总结, 并写了寻路算法的代码, 觉得有用的同学可以看看. 另外因为图片制作起来比较麻烦, 所以我用的是原文里的图片.
当然寻路算法不止 A* 这一种, 还有递归, 非递归, 广度优先, 深度优先, 使用堆栈等等, 有兴趣的可以研究研究~~

简易地图

这里写图片描述
如图所示简易地图, 其中绿色方块的是起点 (用 A 表示), 中间蓝色的是障碍物, 红色的方块 (用 B 表示) 是目的地. 为了可以用一个二维数组来表示地图, 我们将地图划分成一个个的小方块.
二维数组在游戏中的应用是很多的, 比如贪吃蛇和俄罗斯方块基本原理就是移动方块而已. 而大型游戏的地图, 则是将各种”地貌”铺在这样的小方块上.

寻路步骤

  1. 从起点A开始, 把它作为待处理的方格存入一个”开启列表”, 开启列表就是一个等待检查方格的列表.
  2. 寻找起点A周围可以到达的方格, 将它们放入”开启列表”, 并设置它们的”父方格”为A.
  3. 从”开启列表”中删除起点 A, 并将起点 A 加入”关闭列表”, “关闭列表”中存放的都是不需要再次检查的方格

这里写图片描述
图中浅绿色描边的方块表示已经加入 “开启列表” 等待检查. 淡蓝色描边的起点 A 表示已经放入 “关闭列表” , 它不需要再执行检查.
从 “开启列表” 中找出相对最靠谱的方块, 什么是最靠谱? 它们通过公式 F=G+H 来计算.

F = G + H
G 表示从起点 A 移动到网格上指定方格的移动耗费 (可沿斜方向移动).
H 表示从指定的方格移动到终点 B 的预计耗费 (H 有很多计算方法, 这里我们设定只可以上下左右移动).

这里写图片描述

我们假设横向移动一个格子的耗费为10, 为了便于计算, 沿斜方向移动一个格子耗费是14. 为了更直观的展示如何运算 FGH, 图中方块的左上角数字表示 F, 左下角表示 G, 右下角表示 H. 看看是否跟你心里想的结果一样?
从 “开启列表” 中选择 F 值最低的方格 C (绿色起始方块 A 右边的方块), 然后对它进行如下处理:

  1. 把它从 “开启列表” 中删除, 并放到 “关闭列表” 中.
  2. 检查它所有相邻并且可以到达 (障碍物和 “关闭列表” 的方格都不考虑) 的方格. 如果这些方格还不在 “开启列表” 里的话, 将它们加入 “开启列表”, 计算这些方格的 G, H 和 F 值各是多少, 并设置它们的 “父方格” 为 C.
  3. 如果某个相邻方格 D 已经在 “开启列表” 里了, 检查如果用新的路径 (就是经过C 的路径) 到达它的话, G值是否会更低一些, 如果新的G值更低, 那就把它的 “父方格” 改为目前选中的方格 C, 然后重新计算它的 F 值和 G 值 (H 值不需要重新计算, 因为对于每个方块, H 值是不变的). 如果新的 G 值比较高, 就说明经过 C 再到达 D 不是一个明智的选择, 因为它需要更远的路, 这时我们什么也不做.

这里写图片描述
如图, 我们选中了 C 因为它的 F 值最小, 我们把它从 “开启列表” 中删除, 并把它加入 “关闭列表”. 它右边上下三个都是墙, 所以不考虑它们. 它左边是起始方块, 已经加入到 “关闭列表” 了, 也不考虑. 所以它周围的候选方块就只剩下 4 个. 让我们来看看 C 下面的那个格子, 它目前的 G 是14, 如果通过 C 到达它的话, G将会是 10 + 10, 这比 14 要大, 因此我们什么也不做.
然后我们继续从 “开启列表” 中找出 F 值最小的, 但我们发现 C 上面的和下面的同时为 54, 这时怎么办呢? 这时随便取哪一个都行, 比如我们选择了 C 下面的那个方块 D.
这里写图片描述
D 右边已经右上方的都是墙, 所以不考虑, 但为什么右下角的没有被加进 “开启列表” 呢? 因为如果 C 下面的那块也不可以走, 想要到达 C 右下角的方块就需要从 “方块的角” 走了, 在程序中设置是否允许这样走. (图中的示例不允许这样走)
这里写图片描述
就这样, 我们从 “开启列表” 找出 F 值最小的, 将它从 “开启列表” 中移掉, 添加到 “关闭列表”. 再继续找出它周围可以到达的方块, 如此循环下去…
那么什么时候停止呢? —— 当我们发现 “开始列表” 里出现了目标终点方块的时候, 说明路径已经被找到.

如何找回路径

这里写图片描述
如上图所示, 除了起始方块, 每一个曾经或者现在还在 “开启列表” 里的方块, 它都有一个 “父方块”, 通过 “父方块” 可以索引到最初的 “起始方块”, 这就是路径.

将整个过程抽象

把起始格添加到 “开启列表”
do
{
寻找开启列表中F值最低的格子, 我们称它为当前格.
把它切换到关闭列表.
对当前格相邻的8格中的每一个
if (它不可通过 || 已经在 “关闭列表” 中)
{
什么也不做.
}
if (它不在开启列表中)
{
把它添加进 “开启列表”, 把当前格作为这一格的父节点, 计算这一格的 FGH
if (它已经在开启列表中)
{
if (用G值为参考检查新的路径是否更好, 更低的G值意味着更好的路径)
{
把这一格的父节点改成当前格, 并且重新计算这一格的 GF 值.
}
} while( 目标格已经在 “开启列表”, 这时候路径被找到)
如果开启列表已经空了, 说明路径不存在.

最后从目标格开始, 沿着每一格的父节点移动直到回到起始格, 这就是路径.

主要代码

程序中的 “开启列表” 和 “关闭列表”

List<Point> CloseList;
List<Point> OpenList;

Point 类

public class Point
{public Point ParentPoint { get; set; }public int F { get; set; }  //F=G+Hpublic int G { get; set; }public int H { get; set; }public int X { get; set; }public int Y { get; set; }public Point(int x, int y){this.X = x;this.Y = y;}public void CalcF(){this.F = this.G + this.H;}
}

寻路过程

public Point FindPath(Point start, Point end, bool IsIgnoreCorner)
{OpenList.Add(start);while (OpenList.Count != 0){//找出F值最小的点var tempStart = OpenList.MinPoint();OpenList.RemoveAt(0);CloseList.Add(tempStart);//找出它相邻的点var surroundPoints = SurrroundPoints(tempStart, IsIgnoreCorner);foreach (Point point in surroundPoints){if (OpenList.Exists(point))//计算G值, 如果比原来的大, 就什么都不做, 否则设置它的父节点为当前点,并更新G和FFoundPoint(tempStart, point);else//如果它们不在开始列表里, 就加入, 并设置父节点,并计算GHFNotFoundPoint(tempStart, end, point);}if (OpenList.Get(end) != null)return OpenList.Get(end);}return OpenList.Get(end);
}

完整代码
maze.cs

using System;
using System.Collections.Generic;
using System.Linq;namespace Maze
{class Maze{public const int OBLIQUE = 14;public const int STEP = 10;public int[,] MazeArray { get; private set; }List<Point> CloseList;List<Point> OpenList;public Maze(int[,] maze){this.MazeArray = maze;OpenList = new List<Point>(MazeArray.Length);CloseList = new List<Point>(MazeArray.Length);}public Point FindPath(Point start, Point end, bool IsIgnoreCorner){OpenList.Add(start);while (OpenList.Count != 0){//找出F值最小的点var tempStart = OpenList.MinPoint();OpenList.RemoveAt(0);CloseList.Add(tempStart);//找出它相邻的点var surroundPoints = SurrroundPoints(tempStart, IsIgnoreCorner);foreach (Point point in surroundPoints){if (OpenList.Exists(point))//计算G值, 如果比原来的大, 就什么都不做, 否则设置它的父节点为当前点,并更新G和FFoundPoint(tempStart, point);else//如果它们不在开始列表里, 就加入, 并设置父节点,并计算GHFNotFoundPoint(tempStart, end, point);}if (OpenList.Get(end) != null)return OpenList.Get(end);}return OpenList.Get(end);}private void FoundPoint(Point tempStart, Point point){var G = CalcG(tempStart, point);if (G < point.G){point.ParentPoint = tempStart;point.G = G;point.CalcF();}}private void NotFoundPoint(Point tempStart, Point end, Point point){point.ParentPoint = tempStart;point.G = CalcG(tempStart, point);point.H = CalcH(end, point);point.CalcF();OpenList.Add(point);}private int CalcG(Point start, Point point){int G = (Math.Abs(point.X - start.X) + Math.Abs(point.Y - start.Y)) == 2 ? STEP : OBLIQUE;int parentG = point.ParentPoint != null ? point.ParentPoint.G : 0;return G + parentG;}private int CalcH(Point end, Point point){int step = Math.Abs(point.X - end.X) + Math.Abs(point.Y - end.Y);return step * STEP;}//获取某个点周围可以到达的点public List<Point> SurrroundPoints(Point point, bool IsIgnoreCorner){var surroundPoints = new List<Point>(9);for(int x = point.X -1; x <= point.X+1;x++)for (int y = point.Y - 1; y <= point.Y + 1; y++){if (CanReach(point,x, y,IsIgnoreCorner))surroundPoints.Add(x, y);}return surroundPoints;}//在二维数组对应的位置不为障碍物private bool CanReach(int x, int y){return MazeArray[x, y] == 0;}public bool CanReach(Point start, int x, int y, bool IsIgnoreCorner){if (!CanReach(x, y) || CloseList.Exists(x, y))return false;else{if (Math.Abs(x - start.X) + Math.Abs(y - start.Y) == 1)return true;//如果是斜方向移动, 判断是否 "拌脚"else{if (CanReach(Math.Abs(x - 1), y) && CanReach(x, Math.Abs(y - 1)))return true;elsereturn IsIgnoreCorner;}}}}//Point 类型public class Point{public Point ParentPoint { get; set; }public int F { get; set; }  //F=G+Hpublic int G { get; set; }public int H { get; set; }public int X { get; set; }public int Y { get; set; }public Point(int x, int y){this.X = x;this.Y = y;}public void CalcF(){this.F = this.G + this.H;}}//对 List<Point> 的一些扩展方法public static class ListHelper{public static bool Exists(this List<Point> points, Point point){foreach (Point p in points)if ((p.X == point.X) && (p.Y == point.Y))return true;return false;}public static bool Exists(this List<Point> points, int x, int y){foreach (Point p in points)if ((p.X == x) && (p.Y == y))return true;return false;}public static Point MinPoint(this List<Point> points){points = points.OrderBy(p => p.F).ToList();return points[0];}public static void Add(this List<Point> points, int x, int y){Point point = new Point(x, y);points.Add(point);}public static Point Get(this List<Point> points, Point point){foreach (Point p in points)if ((p.X == point.X) && (p.Y == point.Y))return p;return null;}public static void Remove(this List<Point> points, int x, int y){foreach (Point point in points){if (point.X == x && point.Y == y)points.Remove(point);}}}
}

program.cs

using System;namespace Maze
{class Program{static void Main(string[] args){int[,] array = {{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},{ 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1},{ 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1},{ 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1},{ 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1},{ 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1},{ 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1},{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}};Maze maze = new Maze(array);Point start = new Point(1, 1);Point end = new Point(6, 10);var parent = maze.FindPath(start, end, false);Console.WriteLine("Print path:");while (parent != null){Console.WriteLine(parent.X + ", " + parent.Y);parent = parent.ParentPoint;}}}
}

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

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

相关文章

路考口诀

路考口诀一 一踩&#xff08;踩离合&#xff09;、二挂&#xff08;挂一档&#xff09;、三看&#xff08;看倒车镜&#xff09;、四转&#xff08;转向灯&#xff09;、五按&#xff08;按喇叭&#xff09;、六手刹、七走 路考口诀二 01.路考之道很轻松&#xff0c;牢…

nfs服务器工作原理

https://www.cnblogs.com/me80/p/7464125.html转载于:https://www.cnblogs.com/huhuxixi/p/11203049.html

玩转数据结构——均摊复杂度和防止复杂度的震荡(笔记)

数据规模 时间复杂度 并不是所有的双层循环都是O&#xff08;n^2&#xff09;的 复杂度实验来确定复杂度 // O(N) 两倍增加 int findMax( int arr[], int n ){assert( n > 0 );int res arr[0];for( int i 1 ; i < n ; i )if( arr[i] > res )res arr[i];return res…

解决:bash: vim: command not found、docker 容器不识别 vi / vim 、docker 容器中安装 vim

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1. 在 Docker 容器中编辑文件&#xff0c;报错如下&#xff1a; bash: vim: command not found2. 安装 vim &#xff1a; apt-get in…

路考

路考基本项目组成 路考即是科目三&#xff0c;是新增加的一个考试项目&#xff0c;基本项目有13项&#xff0c;包括上车准备、起步、直线行驶、变更车道、通过路口、靠边停车、通过人行横道线、通过学校区域、通过公共汽车站、会车、超车、掉头、夜间行驶。 上车准备 …

OpenDDS通讯rtps_discovery对等发现模式的pub和sub匹配的日志

OpenDDS的通讯体系中&#xff0c;提供了丰富的日志输出&#xff0c;通过日志输出可以清晰的看到pub和sub方的主题匹配的过程&#xff0c;是加深对OpenDDS过程了解的一个好方法。 下面的日志&#xff0c;以OpenDDS3.8为基础&#xff0c;增加了部分日志和时间戳输出。 rtps_dis…

Developing Web Applications with Apache, MySQL, memcached, and Perl

Developing Web Applications with Apache, MySQL, memcached, and Perl转载于:https://www.cnblogs.com/gavinhughhu/archive/2009/11/02/1594290.html

awk 中 {print $1} 什么意思

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 举个例子 echo "aa bb cc" | awk -F {print $1} 结果就是aa&#xff0c;意思是把字符串按空格分割&#xff0c;取第一个。aw…

有驾照不等于会开车,教你开车技巧27招

当今有车的人真的是越来越多了&#xff0c;不要以为自己有驾照就是会开车了哦&#xff0c;其实开车还是有很多技巧的。下面就跟小编看下学会那些招数才真算会开车吧。 1、上车先看车 上车前绕车转一圈&#xff0c;看车的外况、轮胎、车底下有没有漏油漏水。一个星期还得揭开盖子…

OpenDDS通讯中rtps_discovery对等发现的基本配置和说明

OpenDDS的对等发现模式中&#xff0c;可以采用组播或单播方式进行发现和基于主题的DataReader和DataWriter的匹配&#xff0c;下面是一个简单的配置样例&#xff1a; [common] DCPSGlobalTransportConfig$file ORBDebugLevel0 DCPSDebugLevel3 DCPSTransportDebugLevel0 ORBLo…

用户使用协议

知乎协议&#xff08;草案&#xff09; 欢迎您来到知乎。 请您仔细阅读以下条款&#xff0c;如果您对本协议的任何条款表示异议&#xff0c;您可以选择不进入知乎。当您注册成功&#xff0c;无论是进入知乎&#xff0c;还是在知乎上发布任何内容&#xff08;即「内容」&#xf…

解决: bash: unzip: command not found、linux 安装 zip 命令

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1. 执行解压命令报错&#xff1a; bash: unzip: command not found 2. 安装 zip&#xff1a; yum install -y unzip zip 3. 重试成功…

基于OpenDDS开发发布订阅HelloMsg程序的过程(Windows)

基于OpenDDS的应用开发&#xff0c;主要分两个部分的工作&#xff1a; &#xff08;1&#xff09;定义自己的IDL文件&#xff0c;并编译成消息数据类型通讯动态库&#xff1b; &#xff08;2&#xff09;分别编写pub和sub程序&#xff0c;运行 具体步骤&#xff0c;有以下几…

面试后的总结

面试中的收获&#xff1a; 优点&#xff1a; 1. 设计用例考虑较为全面。 2. 自动化&#xff0c;性能都有涉猎&#xff0c;但不深入。 3. 对业务理解较深入。 缺点&#xff1a; 1. 接口自动化停留在初级阶段。 2. UI自动化了解较少。 3. 性能压测缺少数据清洗等步骤。 4. 算法还…

怎样正确使用车灯?

当我们被对面来车明晃晃的远光灯照得意识模糊时&#xff0c;当你快速接近一辆摩托车却发现那是一辆坏了一盏尾灯的卡车时&#xff0c;或是当你前方的小车忽然亮起倒车灯却在往前行驶&#xff0c;最后意识到那只是因为刹车灯与倒车灯线路颠倒时&#xff0c;你就会发现在人们都认…

如何配置DDS以使用多个网络接口?How do I configure DDS to work with multiple network interfaces?

最近在使用OpenDDS的时候遇到一个问题&#xff1a;存在多个虚拟网卡时&#xff0c;发布&#xff08;订阅&#xff09;端重新连接时会阻塞几分钟&#xff0c;在外网找到一篇与此相关的文章。 You cannot specify which NICs DDS will use to send data. You can restrict the NI…

oracle赋予一个用户查询另一个用户中所有表

说明&#xff1a;让用户selame能够查询用户ame中的所有表&#xff08;不能添加和删除&#xff09;1.创建用户selamecreate user selame identified by Password;2.设置用户selame系统权限grant connect,resource to selame; 3.设置用户selame对象权限 grant select any table t…

使用可靠多播与OPENDDS进行数据分发

介绍 也许应用程序设计人员在创建分布式系统时面临的最关键决策之一是如何在感兴趣的各方之间交换数据。通常&#xff0c;这涉及选择一个或多个通信协议并确定向每个端点分派数据的最有效手段。实现较低级别的通信软件可能是耗时的&#xff0c;昂贵的并且容易出错。很多时候&a…

考试 驾校

您的孩子在车里安全么&#xff1f;儿童座椅那点事儿 儿童安全座椅用最最普通的话来解释就是一种系于汽车座位上,供小童乘坐,有束缚设备,并能在发生车祸时,束缚着小童以保障小童安全的座椅。 儿童安全座椅在欧美发达国家已经得到了普遍使用&#xff0c;这些国家基本上都制定了相…

margin为负值的几种情况

1、margin-top为负值像素 margin-top为负值像素&#xff0c;偏移值相对于自身&#xff0c;其后元素受影响&#xff0c;见如下代码&#xff1a; 1 <!DOCTYPE html>2 <html lang"zh">3 <head>4 <meta charset"UTF-8" />5 &l…