浅谈A*算法的一个C#实现

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

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

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

         F = H + G

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

1private int G(int parent)
2{
3    int d = 10;
4    return d + parent;
5}
6private int H(int x, int y, Point end)
7{
8    return (Math.Abs(x - end.X) + Math.Abs(y - end.Y)) * 10;
9}
为了进行寻路的计算,我们还需要一个类来保存针对地图上某些点遍历信息的记录,比如这个点的F、G、H值各是多少,这个点的坐标以及到达这个点的上一个点的坐标。

 1class PathNode : IComparable  2{  3    public int G;  4    public int H;  5    public int F {  6        get{  7            return G + H;  8        }  9    } 10 11    public PathNode Parent; 12    public Point Position; 13 14    public PathNode(Point pos) 15    { 16        this.Position = pos; 17        this.Parent = null; 18        this.G = 0; 19        this.H = 0; 20    } 21 22    public override string ToString() 23    { 24        return Position.ToString(); 25    } 26 27    IComparable Members#region IComparable Members 28    public int CompareTo(PathNode other) 29    { 30        return F - other.F; 31    } 32    #endregion 33}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.

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

在实现实际的算法时,还需要准备3个容器对象:

private List unLockList = new List();
private Dictionary lockList = new Dictionary();
private List path = new List();

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

 1public List FindPath()  2{  3    unLockList.Clear();  4    lockList.Clear();  5    path.Clear();  6    doFindPath();  7    path.Reverse();  8    return path;  9} 10 11private void doFindPath() 12{ 13    PathNode start = new PathNode(Start); 14    PathNode cur = start; 15    while (true) 16    { 17        if(!lockList.ContainsKey(cur.ToString())) 18            lockList.Add(cur.ToString(), cur); 19        for (int i = 0; i < delta.Length; i++) 20        { 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)) 28            { 29                PathNode ulnode = __pn; 30                int newg = G(cur.G); 31                if (newg < ulnode.G) 32                { 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)) 51        { 52            while (cur != null) 53            { 54                path.Add(cur); 55                cur = cur.Parent; 56            } 57            break; 58        } 59    } 60} 61 62private PathNode __pn; 63 64private bool isPointInUnlockList(Point src) 65{ 66    __pn = null; 67    foreach (PathNode item in unLockList) 68    { 69        if (item.Position.Equals(src)) 70        { 71            __pn = item; 72            return true; 73        } 74 75    } 76    return false; 77} 78 79private bool canWalkOnIt(Point node) 80{ 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}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.

没写什么注释,但思路就是上文中的“A*方法总结”。在此就不重新粘贴一遍了。在此需要多啰嗦两句的是,关闭列表用了一个Dictionary,其实关闭列表的目的就是查找下一个点是否在关闭列表当中,用Dictionary的ContainsKey方法比较容易,毕竟在Hashtable中找个Key总比在List中找个元素要快。

为了简化C#实现算法,这里只是遍历了当前点的上下左右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}
};
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

另一个C#实现过程中需要注意的地方是如果4个偏移后的新点包含在开启列表中,那么应该是对开启列表中对应的PathNode的G值进行更新。如果是重新new一个新的PathNode,然后再加入开启列表,那么算法就会出现问题,有可能会陷入无限循环。

对于寻路的结果获取无非就是对PathNode链表中每个PathNode进行遍历,然后放到一个List中再Reverse一下。对于地图来说,这里用的是一个int数组,元素小于0的时候代表不能通过。而A*算法计算出的结果可能并不是最优的结果,不过其效率还是比较高的,原因在于有了评分函数的帮助可以遍历更少的节点。

最后,还是贴上整个Demo项目的文件吧,结构和代码看起来可能并不优雅。

【编辑推荐】

  1. 为ASP.NET控件加入快捷菜单
  2. 为Asp.net控件写单元测试(ViewState)
  3. ASP.NET开发程序过程中值得注意的两个地方
  4. ASP.NET、JSP和PHP究竟哪个好
  5. ASP.NET页面请求原理浅析
责任编辑:彭凡 来源: cnblogs
相关推荐

2009-08-31 13:53:03

C#创建一个文件

2009-06-16 10:20:05

多继承C#

2009-08-17 17:16:19

C#实现在线升级

2024-06-05 08:17:37

C#算法数据科学

2009-07-30 18:18:27

C#时间计算

2009-08-18 17:19:33

C#事件模型

2011-03-29 09:14:49

Dispose模式C#

2011-09-21 10:56:31

C#结构

2009-08-20 18:30:33

C# ReaderWr

2009-08-19 14:15:42

C# 复合控件

2009-08-31 14:01:50

C#创建一个文件

2009-08-26 15:53:42

C#数据访问XML

2009-08-25 01:46:00

C# WINDOWS服

2009-08-14 10:51:43

2009-09-17 17:13:54

C#数组

2009-08-14 00:55:21

C#程序编译

2009-09-11 12:17:59

C#控件属性

2009-09-11 09:11:09

2009-08-14 17:58:05

C#接口方法

2009-08-26 13:15:38

C#选择控制
点赞
收藏

51CTO技术栈公众号