最短路径问题是网络GIS中常见的问题,是许多应用功能的基础,可以实现的算法很多,本文讨论的是使用效率很高的 A*寻路算法实现。
        参考两篇A*算法的经典文章:
                URL:http://www.gamedev.net/reference/articles/article2003.asp
                URL:http://www.policyalmanac.org/games/binaryHeaps.htm
        译文可以在网上找到,有需要可以查阅先。

A*算法的精髓概括如下:
        我们判断最短路径的依据为 F = G + H,这里,G=从起点A到一个可行的结点B的权值开销, H=从当前结点到目的结点的估计权值开销。这种方式常叫做试探法,其实之所以叫做试探法是因为这只是一个猜测。在找到路径之前我们实际上并不知道实际的距离,因为从当前结点到终点存在多种路径,不同的计算H值的方法,在分析过程中是不完全一样的,最终结果路径也可能不一样(当路径权值相同时,我们的最短路径可以不一样)。本文中用两结点间直线距离计算H值。我们采用两个列表(本文用hash表实现),开放列表和关闭列表,开放列表存放待分析结点,关闭列表存放已分析结点。

1.  将开始节点放入开放列表(开始节点的F和G值都视为0);

2.  重复以下步骤:
          a     在开放列表中查找具有最小F值的节点,并把查找到的节点作为当前节点;
          b     把当前节点从开放列表删除, 加入到封闭列表;
          c     对当前节点相邻的每一个节点依次执行以下步骤:
   <1>.     如果该相邻节点不可通行或者该相邻节点已经在封闭列表中,则什么操作也不执行,继续检验下一个节点;
   <2>.     如果该相邻节点不在开放列表中,则将该节点添加到开放列表中, 并将该相邻节点的父节点设为当前节点,同时保存该相邻节点的G和F值;
   <3>.     如果该相邻节点在开放列表中, 则判断若经由当前节点到达该相邻节点的G值是否小于原来保存的G值,若小于,则将该相邻节点的父节点设为当前节点,并重新设置该相邻节点的G和F值.
           d    循环结束条件:
当终点节点被加入到开放列表作为待检验节点时, 表示路径被找到,此时应终止循环;
或者当开放列表为空,表明已无可以添加的新节点,而已检验的节点中没有终点节点则意味着路径无法被找到,此时也结束循环;
3.     从终点节点开始沿父节点遍历, 直到起点,遍历所得的节点就是最后得到的路径;

        “最短路径”问题只是一般的说法,我们可以根据权值代表的意义扩展开来,解决“最优”、“最小”、“最长”等等问题,进一步解决“次优”类问题,这其中就有运筹学的概念了。我们可以在此基础上应用到我们实际项目的问题当中,后面将有介绍。
          A*算法中影响效率的关键应该是在每次选择下一路径结点比较权值的地方,我们采用参考文章中推荐的二叉堆结构,堆顶为最小权值边。实现代码如下:

  1 #region 二叉堆数据结构,用于OpenList结构
  2
  3    //针对A*算法设计的二叉堆结构,用于OpenList结构
  4    //KeyID对应PtID,所以唯一
  5    //Fn可能重复
  6    //按Fn维护堆结构
  7    //xc 2006.07.04
  8    public class BinaryHeapItem
  9    {
 10        public Double Fn;
 11        public string KeyID;
 12    }
 13
 14    public interface iNETA_BinaryHeapM
 15    {
 16        bool AddItem(BinaryHeapItem Item);
 17        bool MinishItemFnValue(string KeyID, Double NewFn);//A*应用中只会减小Fn
 18        BinaryHeapItem GetAndRemoveMinFnItem();
 19        void Clear();
 20        int ItemCount{get;}
 21    }
 22
 23    //Singleton
 24    public class NETA_BinaryHeap:iNETA_BinaryHeapM
 25    {
 26        public static readonly NETA_BinaryHeap NETA_BinaryHeapInstance = new NETA_BinaryHeap();
 27        private System.Collections.Hashtable BinaryHeap=new System.Collections.Hashtable();
 28
 29        private NETA_BinaryHeap() { }
 30        
 31
 32        #region iNETA_BinaryHeapM 成员
 33
 34        public bool AddItem(BinaryHeapItem Item)
 35        {
 36            try
 37            {
 38                if (BinaryHeap.Count == 0)
 39                    BinaryHeap.Add(1, Item);
 40                else
 41                {
 42                    BinaryHeap.Add(BinaryHeap.Count + 1, Item);
 43                    int index = BinaryHeap.Count;
 44                    int temp;
 45                    double temp1;
 46                    BinaryHeapItem tempItem;
 47                    bool isOK = false;
 48                    while (!isOK)
 49                    {
 50                        temp1=index / 2;
 51                        temp = (int)System.Math.Floor(temp1);
 52                        if (temp <= 0) break;
 53
 54                        if (((BinaryHeapItem)BinaryHeap[temp]).Fn > ((BinaryHeapItem)BinaryHeap[index]).Fn)
 55                        {
 56                            tempItem = (BinaryHeapItem)BinaryHeap[temp];
 57                            BinaryHeap[temp] = (BinaryHeapItem)BinaryHeap[index];
 58                            BinaryHeap[index] = tempItem;
 59                            index = temp;
 60                        }
 61                        else
 62                            isOK = true;
 63                    }
 64
 65                }
 66                return true;
 67            }
 68            catch (Exception e)
 69            {
 70                MessageBox.Show("NETA_BinaryHeap.AddItem错误" + e.Message);
 71                return false;
 72            }
 73        }
 74        
 75        public BinaryHeapItem GetAndRemoveMinFnItem()
 76        {
 77            try
 78            {
 79                if (BinaryHeap.ContainsKey(1))
 80                {
 81                    BinaryHeapItem ResultItem = (BinaryHeapItem)BinaryHeap[1];
 82                    BinaryHeapItem ChangeItem = (BinaryHeapItem)BinaryHeap[BinaryHeap.Count];
 83                    BinaryHeap.Remove(BinaryHeap.Count);
 84                    if (BinaryHeap.Count == 0) return ResultItem;
 85                    BinaryHeap[1] = ChangeItem;
 86
 87                    int index = 1;
 88                    int temp;
 89                    BinaryHeapItem tempItem;
 90                    bool isOK = false;
 91                    while (!isOK)
 92                    {
 93                        temp = index * 2;
 94                        if (temp + 1 <= BinaryHeap.Count)
 95                        {
 96                            if ((((BinaryHeapItem)BinaryHeap[index]).Fn > ((BinaryHeapItem)BinaryHeap[temp]).Fn) && (((BinaryHeapItem)BinaryHeap[index]).Fn > ((BinaryHeapItem)BinaryHeap[temp + 1]).Fn))
 97                            {
 98                                if (((BinaryHeapItem)BinaryHeap[temp]).Fn <= ((BinaryHeapItem)BinaryHeap[temp + 1]).Fn)
 99                                {
100                                    tempItem = (BinaryHeapItem)BinaryHeap[temp];
101                                    BinaryHeap[temp] = (BinaryHeapItem)BinaryHeap[index];
102                                    BinaryHeap[index] = tempItem;
103                                    index = temp;
104                                }
105                                else
106                                {
107                                    tempItem = (BinaryHeapItem)BinaryHeap[temp + 1];
108                                    BinaryHeap[temp + 1] = (BinaryHeapItem)BinaryHeap[index];
109                                    BinaryHeap[index] = tempItem;
110                                    index = temp + 1;
111                                }
112                            }
113                            else if (((BinaryHeapItem)BinaryHeap[index]).Fn > ((BinaryHeapItem)BinaryHeap[temp]).Fn)
114                            {
115                                tempItem = (BinaryHeapItem)BinaryHeap[temp];
116                                BinaryHeap[temp] = (BinaryHeapItem)BinaryHeap[index];
117                                BinaryHeap[index] = tempItem;
118                                index = temp;
119                            }
120                            else if (((BinaryHeapItem)BinaryHeap[index]).Fn > ((BinaryHeapItem)BinaryHeap[temp + 1]).Fn)
121                            {
122                                tempItem = (BinaryHeapItem)BinaryHeap[temp + 1];
123                                BinaryHeap[temp + 1] = (BinaryHeapItem)BinaryHeap[index];
124                                BinaryHeap[index] = tempItem;
125                                index = temp + 1;
126                            }
127                            else
128                                isOK = true;
129                        }
130                        else if ((temp == BinaryHeap.Count) && (((BinaryHeapItem)BinaryHeap[index]).Fn > ((BinaryHeapItem)BinaryHeap[temp]).Fn))
131                        {
132                            tempItem = (BinaryHeapItem)BinaryHeap[temp];
133                            BinaryHeap[temp] = (BinaryHeapItem)BinaryHeap[index];
134                            BinaryHeap[index] = tempItem;
135                            index = temp;
136                        }
137                        else
138                            break;
139                    }
140                    return ResultItem;
141                }
142                else
143                    return null;
144            }
145            catch (Exception e)
146            {
147                MessageBox.Show("NETA_BinaryHeap.GetMinFnItem错误" + e.Message);
148                return null;
149            }
150        }
151       
152        public bool MinishItemFnValue(string KeyID, double NewFn)
153        {
154            try
155            {
156                int index=-1;
157                int temp;
158                Double temp1;
159                BinaryHeapItem tempItem;
160                bool isOK = false;
161
162                foreach (DictionaryEntry myDE in BinaryHeap)
163                {
164                    if (((BinaryHeapItem)myDE.Value).KeyID == KeyID)
165                    {
166                        //A*应用中只能减小Fn
167                        if (((BinaryHeapItem)myDE.Value).Fn <= NewFn) return false;//非法Fn
168                        ((BinaryHeapItem)myDE.Value).Fn = NewFn;
169                        index = (int)(myDE.Key);
170                        break;
171                    }
172                }
173                if (index == -1) return false;
174
175                while (!isOK)
176                {
177                    temp1 = index / 2;
178                    temp = (int)System.Math.Floor(temp1);
179                    if (temp <= 0) break;
180
181                    if (((BinaryHeapItem)BinaryHeap[temp]).Fn > ((BinaryHeapItem)BinaryHeap[index]).Fn)
182                    {
183                        tempItem = (BinaryHeapItem)BinaryHeap[temp];
184                        BinaryHeap[temp] = (BinaryHeapItem)BinaryHeap[index];
185                        BinaryHeap[index] = tempItem;
186                        index = temp;
187                    }
188                    else
189                        isOK = true;
190                }
191
192                return true;
193            }
194            catch (Exception e)
195            {
196                MessageBox.Show("NETA_BinaryHeap.MinishItemFnValue错误" + e.Message);
197                return false;
198            }
199        }  
200
201        public int ItemCount
202        {
203            get { return BinaryHeap.Count; }
204        }              
205
206        public void Clear()
207        {
208            BinaryHeap.Clear();
209        }
210
211        #endregion
212    }
213
214#endregion
 
 二叉堆数据结构,用于OpenList结构


有了这个二叉堆结构我们的问题就很好就决了,接下来就是照着前面的算法实现我们的代码。

  1#region NetShortPathAnalyse
  2
  3    public class BLL_NetShortPathAnalyse:IBLL_NetShortPathAnalyse.iNetShortPathAnalyse
  4    {
  5        private IBLL_NetShortPathAnalyse.NETA_NET TheAnalyseNET;
  6        private NETA_BinaryHeap NetShortPathAnalyseBinaryHeap =NETA_BinaryHeap.NETA_BinaryHeapInstance;
  7        private iHnValueComputer ShortPathHnValueComputer;
  8        
  9#region iNetShortPathAnalyse 成员
 10
 11        public IBLL_NetShortPathAnalyse.NETA_NET NETA_AnalyseNET
 12        {
 13            get
 14            {
 15                return TheAnalyseNET;
 16            }
 17            set
 18            {
 19                if (value != null)
 20                    TheAnalyseNET = value;
 21            }        
 22        }
 23
 24        public iHnValueComputer SetHnValueComputer
 25        {
 26            set 
 27            {
 28                if (value != null)
 29                    ShortPathHnValueComputer = value;
 30            }
 31        }
 32
 33        public NETA.IBLL_NetShortPathAnalyse.NETA_Point NetShortPathAnalyse(string StartID, string EndID)
 34        {
 35            try
 36            {
 37                if (TheAnalyseNET == null)
 38                {
 39                    MessageBox.Show("请先初始化网络");
 40                    return null;
 41                }
 42                NETA_Point StartPt = TheAnalyseNET.GetPoint(StartID);
 43                NETA_Point EndPt = TheAnalyseNET.GetPoint(EndID);
 44                if (StartPt == null)
 45                {
 46                    MessageBox.Show("起点在网络中不存在,请重新选择!");
 47                    return null;
 48                }
 49                if (EndPt == null)
 50                {
 51                    MessageBox.Show("终点在网络中不存在,请重新选择!");
 52                    return null;
 53                }
 54                if (StartPt.isEnabled == false || EndPt.isEnabled == false)
 55                {
 56                    MessageBox.Show("起点或终点被设置为障碍点,请重新选择!");
 57                    return null;
 58                }
 59
 60
 61
 62                //
 63                TheAnalyseNET.ResetPointState();
 64                NetShortPathAnalyseBinaryHeap.Clear();
 65
 66
 67
 68                StartPt.Fn = 0;
 69                StartPt.Gn = 0;
 70                StartPt.AnalyseState = PointAnalyseState.InOpenList;
 71
 72                BinaryHeapItem MyItem = new BinaryHeapItem();
 73                MyItem.Fn = StartPt.Fn;
 74                MyItem.KeyID = StartPt.PointID;
 75
 76                NetShortPathAnalyseBinaryHeap.AddItem(MyItem);
 77
 78                NETA_Point CurrPt;
 79                BinaryHeapItem CurrItem;
 80                while (NetShortPathAnalyseBinaryHeap.ItemCount > 0)
 81                {
 82                    CurrItem = NetShortPathAnalyseBinaryHeap.GetAndRemoveMinFnItem();
 83                    CurrPt = TheAnalyseNET.GetPoint(CurrItem.KeyID);
 84                    if (CurrPt.PointID == EndPt.PointID) return CurrPt;
 85
 86                    CurrPt.AnalyseState = PointAnalyseState.InCloseList;
 87                    System.Collections.ArrayList ChildrenPt = TheAnalyseNET.GetChildrenPoints(CurrPt);
 88                    foreach (NETA_Point ChildPt in ChildrenPt)
 89                    {
 90                        if (ChildPt.isEnabled == false || ChildPt.AnalyseState == PointAnalyseState.InCloseList)
 91                        { }
 92                        else if (ChildPt.AnalyseState != PointAnalyseState.InOpenList)
 93                        {
 94                            ChildPt.AnalyseState = PointAnalyseState.InOpenList;
 95                            ChildPt.ParentPoint = CurrPt;
 96                            ChildPt.Gn = CurrPt.Gn + ChildPt.Hn;//Hn起临时储存本次Weight作用
 97                            ChildPt.Hn = ComputeTheHnValue(ChildPt, EndPt);
 98                            ChildPt.Fn = ChildPt.Gn + ChildPt.Hn;
 99                            CurrItem = new BinaryHeapItem();
100                            CurrItem.KeyID = ChildPt.PointID;
101                            CurrItem.Fn = ChildPt.Fn;
102                            NetShortPathAnalyseBinaryHeap.AddItem(CurrItem);
103                        }
104                        else if (ChildPt.AnalyseState == PointAnalyseState.InOpenList)
105                        {
106                            if (CurrPt.Gn + ChildPt.Hn < ChildPt.Gn)
107                            {
108                                ChildPt.ParentPoint = CurrPt;
109                                ChildPt.Gn = CurrPt.Gn + ChildPt.Hn;
110                                ChildPt.Hn = ComputeTheHnValue(ChildPt, EndPt);
111                                ChildPt.Fn = ChildPt.Gn + ChildPt.Hn;
112                                NetShortPathAnalyseBinaryHeap.MinishItemFnValue(ChildPt.PointID, ChildPt.Fn);
113                            }
114                        }
115                    }
116                }
117
118                //if (NetShortPathAnalyseBinaryHeap.ItemCount == 0)
119
120                MessageBox.Show("未找到连通路径!");
121                return null;
122            }
123            catch (Exception e)
124            {
125                MessageBox.Show("BLL_NetShortPathAnalyse.NetShortPathAnalyse错误" + e.Message);
126                return null;
127            }
128        }
129
130        public double ComputeTheHnValue(NETA.IBLL_NetShortPathAnalyse.NETA_Point currPt, NETA.IBLL_NetShortPathAnalyse.NETA_Point EndPt)
131        {
132            try
133            {
134                if (ShortPathHnValueComputer != null)
135                    return ShortPathHnValueComputer.doCompute(currPt, EndPt);
136                else
137                    throw new Exception("未设置'试探搜索'计算因子");
138            }
139            catch(Exception e)
140            {
141                MessageBox.Show("计算因子错误," + e.Message);
142                return 0;
143            }
144        }
145         
146#endregion
147
148    }
149    #endregion

 

      接下来说说实际应用扩展的问题,在网络GIS中我们会实现连通性分析、事故关阀分析、影响用户分析等,在前面的基础上都能够实现。连通性分析,我们需要找到所有连通的路径,采取的方法是通过设置障碍结点的方式多次计算出最短、次短、再次短。。。,直到找不到连通路径为止。 事故关阀分析,分为点事故和线事故,最终我们可以将点事故、事故线的起点、终点以及失效阀门点统一成一种对象即点事故来处理。思路为:分析从各事故点到各源点之间找出所有连通路径,列出每条路径中经过的并且靠近事故点的阀门设施,最后检查这些阀门的必关性。影响用户分析实在前面分析出必关阀门的基础上找到所有相关联的用户要素,处理方式取决于你设计的用户模型表现形式。再说下权值,我们这里处理成线要素的长度,实际情况中我们也会与特定因素相关联,比如分析最佳交通路径时,我们可以与一个模拟交通状况的复杂的数学模型相关联,对于判断依据 F = G + H 中 H 的处理也是一样的。可以说最短路径分析是解决网络GIS中很多问题的基础,我们可以在此基础之上加入很多能够使我们的模型更接近现实情况的模型因素,以达到跟实际应用跟适应。 同样,还是附上一张效果图: