项目中使用到了自动寻路这些功能,正好总结一下A*的各种概念和个人的理解
目录
1、A*算法的基本概念
启发式搜索:
启发式搜索就是在状态空间中的搜索对每一个搜索的位置进行评估,得到最好的位置,再从这个位置进行搜索直到目标。这样可以省略大量无畏的搜索路径,提到了效率。在启发式搜索中,对位置的估价是十分重要的。采用了不同的估价可以有不同的效果。
搜索区域(The Search Area):
图中的搜索区域被划分为了简单的二维数组,数组每个元素对应一个小方格,当然我们也可以将区域等分成是五角星,矩形等,通常将一个单位的中心点称之为搜索区域节点(Node)。
开放列表(Open List):
我们将路径规划过程中待检测的节点存放于Open List中,而已检测过的格子则存放于Close List中。
关闭列表(Close List):
我们将路径规划过程中已经检查过的节点放在Close List。
父节点(parent):
在路径规划中用于回溯的节点。
启发函数(Heuristics Function)(估价函数):
H为启发函数,也被认为是一种试探,由于在找到唯一路径前,我们不确定在前面会出现什么障碍物,因此用了一种计算H的算法,具体根据实际场景决定。在我们简化的模型中,H采用的是传统的曼哈顿距离(Manhattan Distance)估价函数,也就是横纵向走的距离之和。
H(n) = D * (abs ( n.x – goal.x ) + abs ( n.y – goal.y ) )
F(n) = G + H。
F代表当前检查点的总花费,G代表起点到当前检查点的花费,H代表当前检查点到终点的预估花费。
当然,H的函数可以是其他的估价函数, 例如 : 对角线距离和欧几里得距离,比较麻烦 ,可以自己查查
2、A*算法的寻路过程
1.将起点A添加到open列表中(A没有计算花费F是因为当前open列表只有这一个节点)。 2.检查open列表,选取花费F最小的节点M(检查M如果为终点是则结束寻路,如果 open列表 没有则寻路失败,直接结束)。 3.对于与M相邻的每一节点N: 1>如果N是阻挡障碍,那么不管它。 2>如果N在closed列表中,那么不管它。 3>如果N不在open列表中:添加它然后计算出它的花费 F(n)=G+H。 4>如果N已在open列表中:当我们使用当前生成的路径时, 检查F花费是否 更小。如果是,更新它的花费F和它的 父节点。 4.重复2,3步。
3、图解
4、代码
(代码是之前网上的 ,但是有错误,修改了一下 ,可以使用)
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApplication1
{
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);
int count = 0;
while (OpenList.Count != 0)
{
//找出F值最小的点
OpenList = OpenList.OrderBy(open => open.F).ToList();
Point tempStart = OpenList[0];
OpenList.RemoveAt(0);
CloseList.Add(tempStart);
//找出它相邻的点
var surroundPoints = SurrroundPoints(tempStart, IsIgnoreCorner);
foreach (Point point in surroundPoints)
{
if (OpenList.Exists(point))
//计算G值, 如果比原来的大, 就什么都不做, 否则设置它的父节点为当前点,并 更新G和F
FoundPoint(tempStart, point);
else
//如果它们不在开始列表里, 就加入, 并设置父节点,并计算GHF
NotFoundPoint(tempStart, end, point);
}
if (OpenList.Get(end) != null)
return OpenList.Get(end);
if (count>3000) // 循环了3000次跳出 防止卡死
return null;
}
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;
else
return IsIgnoreCorner;
}
}
}
}
//Point 类型
public class Point
{
public Point ParentPoint { get; set; }
public int F { get; set; } //F=G+H
public 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);
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
int[,] array = {
{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
{ 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1},
{ 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1},
{ 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1},
{ 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1},
{ 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1},
{ 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1},
{ 1, 0, 1, 1, 1, 1, 1, 0, 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(7, 7);
Console.WriteLine(System.DateTime.Now.Ticks / 10000);
var parent = maze.FindPath(start, end, false);
List<string> path = new List<string>();
while (parent != null)
{
path.Add(parent.X + "_" + parent.Y);
parent = parent.ParentPoint;
}
Console.WriteLine(System.DateTime.Now.Ticks / 10000);
path.Reverse();
for (int i = 0; i < array.GetLength(0); i++)
{
for (int j = 0; j < array.GetLength(1); j++)
{
string s = i + "_" + j;
if (path.Contains(s))
{
Console.Write("0*");
Console.Write(" ");
}
else
{
Console.Write(array[i, j]);
Console.Write(" ");
}
}
Console.WriteLine();
Console.WriteLine();
}
}
}