最短路径问题是网络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);
<1>. 如果该相邻节点不可通行或者该相邻节点已经在封闭列表中,则什么操作也不执行,继续检验下一个节点;<2>. 如果该相邻节点不在开放列表中,则将该节点添加到开放列表中, 并将该相邻节点的父节点设为当前节点,同时保存该相邻节点的G和F值;<3>. 如果该相邻节点在开放列表中, 则判断若经由当前节点到达该相邻节点的G值是否小于原来保存的G值,若小于,则将该相邻节点的父节点设为当前节点,并重新设置该相邻节点的G和F值.
当终点节点被加入到开放列表作为待检验节点时, 表示路径被找到,此时应终止循环;或者当开放列表为空,表明已无可以添加的新节点,而已检验的节点中没有终点节点则意味着路径无法被找到,此时也结束循环;
“最短路径”问题只是一般的说法,我们可以根据权值代表的意义扩展开来,解决“最优”、“最小”、“最长”等等问题,进一步解决“次优”类问题,这其中就有运筹学的概念了。我们可以在此基础上应用到我们实际项目的问题当中,后面将有介绍。
A*算法中影响效率的关键应该是在每次选择下一路径结点比较权值的地方,我们采用参考文章中推荐的二叉堆结构,堆顶为最小权值边。实现代码如下:
2
3 //针对A*算法设计的二叉堆结构,用于OpenList结构
4 //KeyID对应PtID,所以唯一
5 //Fn可能重复
6 //按Fn维护堆结构
7 //xc 2006.07.04
8 public Class BinaryHeapItemclass BinaryHeapItem
9 {
10 public Double Fn;
11 public string KeyID;
12 }
13
14 public Interface iNETA_BinaryHeapMinterface 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_BinaryHeapclass 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 iNETA_BinaryHeapM 成员#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
有了这个二叉堆结构我们的问题就很好就决了,接下来就是照着前面的算法实现我们的代码。
2
3 public Class BLL_NetShortPathAnalyseclass 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
9iNetShortPathAnalyse 成员#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中很多问题的基础,我们可以在此基础之上加入很多能够使我们的模型更接近现实情况的模型因素,以达到跟实际应用跟适应。 同样,还是附上一张效果图: