目录
1,概述
本文的主要内容是讲述A *寻路算法的基本原理,实现步骤以及对应的C#代码,适合读者用于学习A *算法或
使用此代码提供的接口完成游戏中的寻路功能。
详细的A *算法的原理,请参照:https://blog.youkuaiyun.com/denghecsdn/article/details/78778769
详细的A *算法的实现,请参照:https://www.cnblogs.com/zhoug2020/p/3468167.html
2,A *算法的基本原理
A *算法是一种经典的启发式算法,算法的核心是将节点n到目标点的代价定义为f(n)= g(n)+ h(n),其中 g(n)表示出发点到节点ñ的距离,h(n)是一种启发式函数,表示节点ñ到目标点的评估代价,通常为了简化我们采用曼哈顿距离来模拟。知道 f(n)如何计算后,A *算法还有两个重要的集合——open列表和closed列表,open列表用于存储当前可以选择移动的所有节点,closed列表用于存储走过的所有节点。首先,我们将出发点加入open列表中,然后每次从open列表中选择f(n)最小的节点,然后将该节点从open列表中移除,并加入到closed列表中,并将该节点周围没有走过的所有可达节点加入或更新到open列表中,重复选择节点直到目标点在closed列表中为止,再通过存储节点的parent回溯,这样就可以得到两个点之间的一条最短路径。
3,A *算法的实现步骤
①将出发点加入到open列表中。
②从open列表中选择 f(n)最小的节点k,将节点k从open列表中移除,并将其加入到closed列表中。
③对于节点ķ周围距离为1的每个可达节点t,执行以下操作:
a.如果t在closed列表中,丢弃这个节点;
b.如果t不在open列表中,将其加入open列表中;
c.如果t在open列表中,计算其f(n)并和open列表中该节点的f(n)的对比,如果它的f(n)更小,则更新open列表中该节点的信息。
④重复②③直到目标点在closed列表中(表明求得最短路径)或open列表为空(表明终点不可达)。
4,A *算法的C#实现
文件名:
AStar.cs
算法执行接口:
AStar.Instance.Execute(int [,] map,int srcX,int srcY,int distX,int distY,int reachableVal = 0,bool allowDiagonal = false);
输出路径接口:
AStar.Instance.DisplayPath(ANode aNode);
完整代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
/********************************************************************************
** auth: FengLinyi
** date: 2018/09/01
** desc: A*算法的实现
** Ver.: V1.0.0
*********************************************************************************/
namespace DeepCSharp
{
class AStar
{
/// <summary>
/// 二维坐标点
/// </summary>
public struct Point
{
public int x, y;
public Point(int _x, int _y)
{
x = _x;
y = _y;
}
}
/// <summary>
/// A*的每个节点
/// </summary>
public class ANode
{
public Point point;
public ANode parent;
public int fn, gn, hn;
}
private AStar() { }
public static AStar Instance { get; } = new AStar();
private int[,] map = null;
private Dictionary<Point, ANode> openList = null;
private HashSet<Point> closedList = null;
private Point dist;
private int reachableVal;
/// <summary>
/// 执行算法
/// </summary>
/// <param name="map">二维网格地图,边缘需要用不可达的值填充</param>
/// <param name="srcX">当前点X坐标</param>
/// <param name="srcY">当前点Y坐标</param>
/// <param name="distX">目标点X坐标</param>
/// <param name="distY">目标点Y坐标</param>
public ANode Execute(int[,] map, int srcX, int srcY, int distX, int distY, int reachableVal = 0, bool allowDiagonal = false)
{
openList = new Dictionary<Point, ANode>();
closedList = new HashSet<Point>();
this.map = map;
this.dist = new Point(distX, distY);
this.reachableVal = reachableVal;
//将初始节点加入到open列表中
ANode aNode = new ANode();
aNode.point = new Point(srcX, srcY);
aNode.parent = null;
aNode.gn = 0;
aNode.hn = ManHattan(aNode.point, dist);
aNode.fn = aNode.gn + aNode.hn;
openList.Add(aNode.point, aNode);
while (openList.Count > 0)
{
//从open列表中找到f(n)最小的结点
ANode minFn = FindMinFn(openList);
Point point = minFn.point;
//判断是否到达终点
if (point.x == dist.x && point.y == dist.y) return minFn;
//去除minFn,加入到closed列表中
openList.Remove(minFn.point);
closedList.Add(minFn.point);
//将minFn周围的节点加入到open列表中
AddToOpenList(new Point(point.x - 1, point.y), minFn); //左
AddToOpenList(new Point(point.x + 1, point.y), minFn); //右
AddToOpenList(new Point(point.x, point.y - 1), minFn); //上
AddToOpenList(new Point(point.x, point.y + 1), minFn); //下
if(allowDiagonal)
{
AddToOpenList(new Point(point.x - 1, point.y - 1), minFn); //左上
AddToOpenList(new Point(point.x + 1, point.y - 1), minFn); //右上
AddToOpenList(new Point(point.x - 1, point.y + 1), minFn); //左下
AddToOpenList(new Point(point.x + 1, point.y + 1), minFn); //右下
}
}
return null;
}
/// <summary>
/// 输出最短路径
/// </summary>
/// <param name="aNode"></param>
public void DisplayPath(ANode aNode)
{
while(aNode != null)
{
Console.WriteLine(aNode.point.x + "," + aNode.point.y);
aNode = aNode.parent;
}
}
/// <summary>
/// 判断节点是否可达,可达则将节点加入到open列表中
/// </summary>
/// <param name="a"></param>
/// <param name="parent"></param>
private void AddToOpenList(Point point, ANode parent)
{
if(IsReachable(point) && !closedList.Contains(point))
{
ANode aNode = new ANode();
aNode.point = point;
aNode.parent = parent;
aNode.gn = parent.gn + 1;
aNode.hn = ManHattan(point, dist);
aNode.fn = aNode.gn + aNode.hn;
if (openList.ContainsKey(aNode.point))
{
if (aNode.fn < openList[aNode.point].fn)
{
openList[aNode.point] = aNode;
}
}
else
openList.Add(aNode.point, aNode);
}
}
/// <summary>
/// 判定该点是否可达
/// </summary>
/// <param name="a"></param>
/// <returns></returns>
private bool IsReachable(Point a)
{
return map[a.y, a.x] == this.reachableVal;
}
/// <summary>
/// 计算两个点之间的曼哈顿距离
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
private int ManHattan(Point a, Point b)
{
return Math.Abs(a.x - b.x) + Math.Abs(a.y - b.y);
}
/// <summary>
/// 从open列表中获取最小f(n)的节点
/// </summary>
/// <param name="aNodes"></param>
/// <returns></returns>
private ANode FindMinFn(Dictionary<Point, ANode> aNodes)
{
ANode minANode = null;
foreach(var e in aNodes)
{
if(minANode == null || e.Value.fn < minANode.fn)
{
minANode = e.Value;
}
}
return minANode;
}
}
}
5,测试
主函数中的代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DeepCSharp
{
class Program
{
static void Main(string[] args)
{
int[,] map =
{
{1,1,1,1,1,1,1,1 },
{1,0,0,0,1,1,1,1 },
{1,1,1,0,1,1,1,1 },
{1,1,0,0,1,0,0,1 },
{1,1,0,0,0,0,0,1 },
{1,1,1,1,1,1,1,1 },
};
var node = AStar.Instance.Execute(map, 1, 1, 6, 4);
AStar.Instance.DisplayPath(node);
}
}
}
测试结果: