首先来一张地图,在此A点为起始点,B点为终点(目的地),中心蓝色的为障碍物,如下图:

wKioL1l8j2aC3tBqAADj__lCd_8419.png-wh_50



寻路步骤:

   1,从点A开始,并且把它作为待处理点存入一个“开启列表”。开启列表就像一张购物清单。尽管现在列表里只有一个元素,但以后就会多起来。你的路径可能会通过它包含的方格,也可能不会。基本上,这是一个待检查方格的列表。
   
2,寻找起点周围所有可到达或者可通过的方格,跳过有墙,水,或其他无法通过地形的方格(障碍物)。也把他们加入开启列表。为所有这些方格保存点A作为“父方格”。当我们想描述路径的时候,父方格的资料是十分重要的。后面会解释它的具体用途。
   
3,从开启列表中删除点A,把它加入到一个“关闭列表”,列表中保存所有不需要再次检查的方格。

wKiom1l8lKLhHnW9AACLWuaH8Xo196.png-wh_50


从 "开启列表" 中找出相对最靠谱的方块, 什么是最靠谱? 它们通过公式 F=G+H 来计算.

G = 从起点A,沿着产生的路径,移动到网格上指定方格的距离。
H = 从网格上那个方格移动到终点B的预估移动距离。

我们假设横向移动一个格子的耗费为10, 为了便于计算, 沿斜方向移动一个格子耗费是14. 为了更直观的展示如何运算 FGH, 图中方块的左上角数字表示 F, 左下角表示 G, 右下角表示 H



为了继续搜索,我们简单的从开启列表中选择F值最低的方格。然后,对选中的方格做如下处理:

wKioL1l8nXqSksV7AAFSIkFx378122.png-wh_50   

   4,把它从开启列表中删除,然后添加到关闭列表中。
   
5,检查所有相邻格子。跳过那些已经在关闭列表中的或者不可通过的(有墙,水的地形,或者其他无法通过的地形),把他们添加进开启列表,如果他们还不在里面的话。把选中的方格作为新的方格的父节点。
   
6,如果某个相邻格已经在开启列表里了,检查现在的这条路径是否更好。换句话说,检查如果我们用新的路径到达它的话,G值是否会更低一些。如果不是,那就什么都不做。
      另一方面,如果新的G值更低,那就把相邻方格的父节点改为目前选中的方格(在上面的图表中,把箭头的方向改为指向这个方格)。最后,重新计算F和G的值。


图解


当A被放入开启列表后 , 按照上面的步骤,寻找其周围F值最小的格子:

wKiom1l8mFbQT9UoAAFElRxNyGY396.png-wh_50

如上图, 我们选中了 C 因为它的 F 值最小, 我们把它从 "开启列表" 中删除, 并把它加入 "关闭列表". 它(C点)右边上下三个都是墙, 所以不考虑它们. 它左边是起始方块(A点), 已经加入到 "关闭列表" 了, 也不考虑. 所以它周围的候选方块就只剩下 4 个. 让我们来看看 C 下面的那个格子(D点,早已在A点的视乎已经加入了开启列表), 它目前的 G 是14, 如果通过 C 到达它的话, G将会是 10 + 10, 这比 14 要大, 因此我们什么也不做.

wKioL1l8mhrh7RJ_AAE6BqCgTIU080.png-wh_50

然后我们继续从 "开启列表" 中找出 F 值最小的, 但我们发现 C 上面的和下面的同时为 54, 这时怎么办呢? 这时随便取哪一个都行, 比如我们选择了 C 下面的那个方块 D.

D 右边以及右上方的都是墙, 所以不考虑, 但为什么右下角的没有被加进 "开启列表" 呢? 因为如果 C 下面的那块也不可以走, 想要到达 C 右下角的方块就需要从 "方块的角" 走了(产生绊脚), 在程序中设置是否允许

这样走.

绊脚解释如下图:

wKiom1l8nFLwuce3AAGHJ2_mx7Y271.png-wh_50

就这样, 我们从 "开启列表" 找出 F 值最小的, 将它从 "开启列表" 中移掉, 添加到 "关闭列表". 再继续找出它周围可以到达的方块, 如此循环下去。

        那么什么时候停止呢:当我们发现 "开始列表" 里出现了目标终点方块的时候, 说明路径已经被找到.


我们测试一下A*

这里需要使用AStarLib类库 , 会在附件中提供。

using AStarLib;
using AStarLib.com;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace AStarTest
{
    class Program
    {
        static void Main(string[] args)
        {
            // 0 : 可走 / 1 : 不可走
            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}
                           };
            AStarHandler aStar = new AStarHandler(array, 10);
            Console.WriteLine("开始寻路 , 开始点 : {0},{1} 目标点 : {2},{3}", 1, 1, 6, 4);
            List<Node> path = aStar.Get_Map_Path(new Node(1, 1), new Node(6, 4), false);
            if (path != null)
            {
                Console.WriteLine("路径为(下标对应的是地图数组的下标,所以从0开始) : ");
                Console.WriteLine("====================================");
                Node cell = null;
                for (int i = 0, j = path.Count; i < j; i++)
                { 
                    cell = path[i];
                    Console.WriteLine("横(第一维)下标{0} , 纵(第二维)下标{1}", cell.Row_Index, cell.Col_Index);
                }
            }
            else
            {
                Console.WriteLine("此地无法到达");
            }
            Console.WriteLine("-------------------------------------------------------------------");
            Console.WriteLine("开始寻路 , 开始点 : {0},{1} 目标点 : {2},{3}", 1, 1, 7, 7);
            path = aStar.Get_Map_Path(new Node(1, 1), new Node(7, 7), false);
            if (path != null)
            {
                Console.WriteLine("路径为(下标对应的是地图数组的下标,所以从0开始) : ");
                Console.WriteLine("====================================");
                Node cell = null;
                for (int i = 0, j = path.Count; i < j; i++)
                {
                    cell = path[i];
                    Console.WriteLine("横(第一维)下标{0} , 纵(第二维)下标{1}", cell.Row_Index, cell.Col_Index);
                }
            }
            else
            {
                Console.WriteLine("此地无法到达");
            }
            Console.Read();
        }
    }
}

结果:

wKioL1l9uyziEjLvAABr7jJRQqI947.png-wh_50