A*算法的一个C#实现

最近正在读云风的《游戏之旅》,看着看着就读到了A*寻路算法,虽然以前没有接触过,但总觉得好奇。于是从网上找了一些资料便开始研究。

当然,主要参考的算法文档是“http://www.vckbase.com/document/viewdoc/?id=1422”不过这里并没有给出实际的源代码。而搜了一下A*算法的代码,大都是ActionScript的源码。毕竟用Flash做一个Demo会方便很多。不过既然都打开了VisualStudio,那么就用C#写一个吧。

A*算法最主要的是对路径的评分函数。而实际应用时,这个函数的设计会产生不同的结果。从上面的文档中我们可以很容易地了解到评分F的公式:

         F = H + G

当然根据文中提到的简便方法,我们可以对HG的计算写出下面的代码。

1 private   int  G( int  parent)
2 ExpandedBlockStart.gifContractedBlock.gif {
3    int d = 10;
4    return d + parent;
5}

6 private   int  H( int  x,  int  y, Point end)
7 ExpandedBlockStart.gifContractedBlock.gif {
8    return (Math.Abs(x - end.X) + Math.Abs(y - end.Y)) * 10;
9}

为了进行寻路的计算,我们还需要一个类来保存针对地图上某些点遍历信息的记录,比如这个点的F、G、H值各是多少,这个点的坐标以及到达这个点的上一个点的坐标。

 1 class  PathNode : IComparable < PathNode >
 2 ExpandedBlockStart.gifContractedBlock.gif {
 3    public int G;
 4    public int H;
 5ExpandedSubBlockStart.gifContractedSubBlock.gif    public int F {
 6ExpandedSubBlockStart.gifContractedSubBlock.gif        get{
 7            return G + H;
 8        }

 9    }

10
11    public PathNode Parent;
12    public Point Position;
13
14    public PathNode(Point pos)
15ExpandedSubBlockStart.gifContractedSubBlock.gif    {
16        this.Position = pos;
17        this.Parent = null;
18        this.G = 0;
19        this.H = 0;
20    }

21
22    public override string ToString()
23ExpandedSubBlockStart.gifContractedSubBlock.gif    {
24        return Position.ToString();
25    }

26
27ContractedSubBlock.gifExpandedSubBlockStart.gif    IComparable Members#region IComparable<PathNode> Members
28    public int CompareTo(PathNode other)
29ExpandedSubBlockStart.gifContractedSubBlock.gif    {
30        return F - other.F;
31    }

32    #endregion

33}

PathNode这个类实现了IComparable接口,目的是为了对PathNode列表进行排序。还记得上面提到的文章中的一句话吗“寻找开启列表中F值最低的格子。我们称它为当前格。”没错,这就是为这个条件做的准备。对于寻找F值最低的“格子”,把开启列表一排序就OK了。
在实现实际的算法时,还需要准备3个容器对象:

private List<PathNode> unLockList = new List<PathNode>();
private Dictionary<string, PathNode> lockList = new Dictionary<string, PathNode>();
private List<PathNode> path = new List<PathNode>();

前两个是算法中提到的“开启列表”和“关闭列表”,最后一个是找到的最终路径。
最后来实现A*算法:

 1 public  List < PathNode >  FindPath()
 2 ExpandedBlockStart.gifContractedBlock.gif {
 3    unLockList.Clear();
 4    lockList.Clear();
 5    path.Clear();
 6    doFindPath();
 7    path.Reverse();
 8    return path;
 9}

10
11 private   void  doFindPath()
12 ExpandedBlockStart.gifContractedBlock.gif {
13    PathNode start = new PathNode(Start);
14    PathNode cur = start;
15    while (true)
16ExpandedSubBlockStart.gifContractedSubBlock.gif    {
17        if(!lockList.ContainsKey(cur.ToString()))
18            lockList.Add(cur.ToString(), cur);
19        for (int i = 0; i < delta.Length; i++)
20ExpandedSubBlockStart.gifContractedSubBlock.gif        {
21            Point newp = new Point(cur.Position.X + delta[i][0],
22                cur.Position.Y + delta[i][1]);
23            if (!canWalkOnIt(newp))
24                continue;
25            if (lockList.ContainsKey(newp.ToString()))
26                continue;
27            if (isPointInUnlockList(newp))
28ExpandedSubBlockStart.gifContractedSubBlock.gif            {
29                PathNode ulnode = __pn;
30                int newg = G(cur.G);
31                if (newg < ulnode.G)
32ExpandedSubBlockStart.gifContractedSubBlock.gif                {
33                    ulnode.Parent = cur;
34                    ulnode.G = newg;
35                }

36                continue;
37            }

38            PathNode newpn = new PathNode(newp);
39            newpn.G = G(cur.G);
40            newpn.H = H(newp.X, newp.Y, End);
41            newpn.Parent = cur;
42            unLockList.Add(newpn);
43        }

44        if (unLockList.Count == 0)
45            break;
46        unLockList.Sort();
47        cur = unLockList[0];
48        unLockList.Remove(cur);
49        
50        if (cur.Position.Equals(End))
51ExpandedSubBlockStart.gifContractedSubBlock.gif        {
52            while (cur != null)
53ExpandedSubBlockStart.gifContractedSubBlock.gif            {
54                path.Add(cur);
55                cur = cur.Parent;
56            }

57            break;
58        }

59    }

60}

61
62 private  PathNode __pn;
63
64 private   bool  isPointInUnlockList(Point src)
65 ExpandedBlockStart.gifContractedBlock.gif {
66    __pn = null;
67    foreach (PathNode item in unLockList)
68ExpandedSubBlockStart.gifContractedSubBlock.gif    {
69        if (item.Position.Equals(src))
70ExpandedSubBlockStart.gifContractedSubBlock.gif        {
71            __pn = item;
72            return true;
73        }

74
75    }

76    return false;
77}

78
79 private   bool  canWalkOnIt(Point node)
80 ExpandedBlockStart.gifContractedBlock.gif {
81    if (node.X < 0 || node.Y < 0)
82        return false;
83    if (node.X > Width - 1 || node.Y > Height - 1)
84        return false;
85    return GetNodeValue(node.X, node.Y) >= 0;
86}

没写什么注释,但思路就是上文中的“A*方法总结”。在此就不重新粘贴一遍了。在此需要多啰嗦两句的是,关闭列表用了一个Dictionary,其实关闭列表的目的就是查找下一个点是否在关闭列表当中,用Dictionary的ContainsKey方法比较容易,毕竟在Hashtable中找个Key总比在List中找个元素要快。
为了简化算法,这里只是遍历了当前点的上下左右4个相邻点。上文中介绍的是遍历8个点的情况,不过这也不是很复杂,只不过麻烦点在于G这个方法,需要判断一下是不是斜向走的。另外对4个相邻点的遍历,方法来源于之前看的一段AS代码,它用了一个偏移量数组来保存8个偏移量。而这里只是保存了4个偏移量。在实际的算法中,循环一下偏移量数组就很方便了(之前见过一个代码并没有用这个方法,而是复制了8短类似的函数调用代码,逻辑上就不如这个看的清晰)。delta数组如下:
private int[][] delta = new int[][]{
    new int[]{0,1},
    new int[]{0,-1},
    new int[]{1,0},
    new int[]{-1,0}
};

另一个需要注意的地方是如果4个偏移后的新点包含在开启列表中,那么应该是对开启列表中对应的PathNode的G值进行更新。如果是重新new一个新的PathNode,然后再加入开启列表,那么算法就会出现问题,有可能会陷入无限循环。
对于寻路的结果获取无非就是对PathNode链表中每个PathNode进行遍历,然后放到一个List中再Reverse一下。对于地图来说,这里用的是一个int数组,元素小于0的时候代表不能通过。而A*算法计算出的结果可能并不是最优的结果,不过其效率还是比较高的,原因在于有了评分函数的帮助可以遍历更少的节点。
最后,还是贴上整个Demo项目的文件吧,结构和代码看起来可能并不优雅。[AStarPathSearch.rar]


转载于:https://www.cnblogs.com/blacktear23/archive/2009/07/22/1528597.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
此为用C#写的A*算法源代码 using System; using System.Collections.Generic; using System.Text; using System.Drawing; namespace EtSoft.AStarLib { public class AStar { private StarNodeCollection openList = new StarNodeCollection(); private StarNodeCollection closeList = new StarNodeCollection(); public StarNode currentNode = null; #region 构造函数 /// <summary> /// 使用指定的地图对象、起点和终点初始化A星算法 /// </summary> /// <param name="map">地图对象</param> public AStar(Map map) { this.map = map; } /// <summary> /// /// </summary> /// <param name="map">地图对象</param> /// <param name="start">起点坐标</param> /// <param name="end">终点坐标</param> public AStar(Map map, Point start, Point end) : this(map) { this.start = new StarNode(start); this.end = new StarNode(end); openList.Add(new StarNode(start)); //AddStartNodeToOpenList(this.start); } /// <summary> /// /// </summary> /// <param name="map">地图对象</param> /// <param name="startX">起点X坐标</param> /// <param name="startY">起点Y坐标</param> /// <param name="endX">终点X坐标</param> /// <param name="endY">终点Y坐标</param> public AStar(Map map, int startX, int startY, int endX, int endY) : this(map, new Point(startX, startY), new Point(endX, endY)) { } #endregion #region 属性 protected Map map; /// <summary> /// 地图数据 /// </summary> public Map Map { get { return map; } set { map = value; } } private StarNode start = null; /// <summary> /// 起点坐标,如果没有设置起点,返回null /// </summary> public StarNode Start { get { return start; } set { start = value; openList.Clear(); openList.Add(start); //AddNodeToOpenList(start); } } private StarNode end = null; /// <summary> /// 终点坐标,如果没有设置终点,返回null /// </summary> public StarNode End { get { return end; } set { end = value; } } private StarNodeCollection path; /// <summary> /// 返回路径节点集合,没有路径则返回null /// </summary> public StarNodeCollection Path { get { return path; } } private bool allDirection = false; /// <summary> /// true,允许八方向寻路 /// </summary> public bool AllDirection { get { return allDirection; } set { allDirection = value; } } #endregion /// <summary> /// 开始寻路 /// </summary> public void StartSearchPath() { if (start == null) throw new InvalidNodeException(InvalidNodeTypes.NoStart); if (end == null) throw new InvalidNodeException(InvalidNodeTypes.NoEnd); path = null; openList.Clear(); closeList.Clear(); currentNode = start; SearchPath(currentNode); } //寻路递归,受保护的虚方法,允许在子类重写寻路算法 protected virtual void SearchPath(StarNode starNode) { //currentNode = starNode; openList.Remove(starNode); closeList.Add(starNode); AddNodeToOpenList(); if (closeList.Contains(end)) { //如果终点在关闭列表中,找到路径 StarNode node=starNode.Parent; path = new StarNodeCollection(); do { path.Add(node); node = node.Parent; } while (node != null && !node.Equals(start)); path.Reverse(); return; } else if (openList.Count == 0) { //终点不在关闭列表,开放列表已空,没有可通行的路径 return; } currentNode = GetNextNode(); //迭代过程 SearchPath(currentNode); } /// <summary> /// 获得当前节点的下一个最佳节点 /// </summary> /// <returns></returns> public StarNode GetNextNode() { openList.SortByF(); return openList[0]; } /// <summary> /// 把当前点周围可通过的点加入到开放列表中 /// </summary>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值