最短路径问题是网络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
二叉堆数据结构,用于OpenList结构
#region 二叉堆数据结构,用于OpenList结构
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
有了这个二叉堆结构我们的问题就很好就决了,接下来就是照着前面的算法实现我们的代码。
1
NetShortPathAnalyse
#region NetShortPathAnalyse
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
9
iNetShortPathAnalyse 成员#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中很多问题的基础,我们可以在此基础之上加入很多能够使我们的模型更接近现实情况的模型因素,以达到跟实际应用跟适应。 同样,还是附上一张效果图:

刚开始写,格式老是调不好。