begin
swap(pos[heap[p]],pos[heap[pdiv2]]);
swap(heap[p],heap[pdiv2]);
p:
=pdiv2;
end;
end;
end;
procedureextract(varid,dis:
integer);{读取堆中最小元素的节点编号和节点1到该节点的距离}
begin
id:
=heap[1];
dis:
=key[id];
dec(count);
if(count>0)then
begin
swap(pos[heap[1]],pos[heap[count+1]]);
swap(heap[1],heap[count+1]);
heapify
(1);
end;
end;
begin
{读取数据}
readln(n);
fori:
=1tondo
begin
read(deg[i]);
forj:
=1todeg[i]doread(list[i,j,1],list[i,j,2]);
end;
{初始化}
fori:
=1tondo
begin
exist[i]:
=true;
pos[i]:
=i;
heap[i]:
=i;
key[i]:
=maxint;
end;
count:
=n;
key[1]:
=0;
{dijkstra算法的主要操作}
while(count>0)do
begin
extract(i,now);
ifnow=maxintthenbreak;
forj:
=1todeg[i]do
ifexist[list[i,j,1]]thenmodify(list[i,j,1],now+list[i,j,2]);
end;
{输出}
ifkey[n]=maxintthenwriteln('NotConnected!
'){节点1和节点n不连通}
elsewriteln(key[n]);{连通}
end.
SampleInput
6
3250310545
2315510
2120415
2220535
1450
143
SPFA算法
求单源最短路的SPFA算法的全称是:
ShortestPathFasterAlgorithm。
最短路径快速算法-SPFA算法是西南交通大学段凡丁于1994年发表的。
适用范围:
给定的图存在负权边,这时类似Dijkstra等算法便没有了用武之地,而Bellman-Ford算法的复杂度又过高,SPFA算法便派上用场了。
我们约定有向加权图G不存在负权回路,即最短路径一定存在。
当然,我们可以在执行该算法前做一次拓扑排序,以判断是否存在负权回路,但这不是我们讨论的重点。
算法思想:
我们用数组d记录每个结点的最短路径估计值,用邻接表来存储图G。
我们采取的方法是动态逼近法:
设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。
这样不断从队列中取出结点来进行松弛操作,直至队列空为止。
实现方法:
建立一个队列,初始时队列里只有起始点,再建立一个表格记录起始点到所有点的最短路径(该表格的初始值要赋为极大值,该点到他本身的路径赋为0)。
然后执行松弛操作,用队列里有的点作为起始点去刷新到所有点的最短路,如果刷新成功且被刷新点不在队列中则把该点加入到队列最后。
重复执行直到队列为空。
判断有无负环:
如果某个点进入队列的次数超过N次则存在负环(SPFA无法处理带负环的图);我们还可以做一次拓扑排序,以判断是否存在负权回路(在没排完之前出现了没有入度为0的点,那么此图一定存在回路)。
单源最短路径中的松弛操作
对于图中的每个顶点v∈V,都设置一个属性d[v],描述从源点s到v的最短路径上权值的上界,称为最短路径估计。
pre[v]代表S到v的当前最短路径中v点之前的一个点的编号,我们用下面的过程来对最短路径估计和前趋进行初始化。
(O(V)时间)。
经过初始化以后,对所有v∈V,pre[v]=NULL,对v∈V-{s},有d[s]=0以及d[v]=∞。
实际松弛过程:
在松弛一条边(u,v)的过程中,要测试是否可以通过u节点,对迄今找到的v的最短路径进行改进;如果可以改进的话,则更新d[v]和pre[v]。
一次松弛操作可以减小最短路径估计的值d[v],并更新v的前趋域pre[v](S到v的当前最短路径中v点之前的一个点的编号)。
下面的伪代码对边(u,v)进行一步松弛操作。
融入实际的单源最短路径的算法中。
RELAX(u,v,w)
if(d[v]>d[u]+w(u,v))
{d[v]=d[u]+w(u,v);
pre[v]=u;}
松弛是改变最短路径和前趋的唯一方式。
求右图a到g的最短路径
首先建立起始点a到其余各点的最短路径表格
a
b
c
d
e
f
g
d[i]
0
∞
∞
∞
∞
∞
∞
首先源点a入队,当队列非空时:
1、队首元素(a)出队,对以a为起始点的所有边的终点依次进行松弛操作(此处有b,c,d三个点),此时路径表格状态为:
a
b
c
d
e
f
g
d[i]
0
24
8
15
∞
∞
∞
在松弛时三个点的最短路径估值变小了,而这些点队列中都没有出现,这些点需要入队,此时,队列中新入队了三个结点b,c,d
队首元素b点出队,对以b为起始点的所有边的终点依次进行松弛操作(此处只有e点),此时路径表格状态为:
a
b
c
d
e
f
g
d[i]
0
24
8
15
30
∞
∞
在最短路径表中,e的最短路径估值也变小了,e在队列中不存在,因此e也要入队,此时队列中的元素为c,d,e
队首元素c点出队,对以c为起始点的所有边的终点依次进行松弛操作(此处有e,f两个点),此时路径表格状态为:
a
b
c
d
e
f
g
d[i]
0
24
8
15
15
11
∞
在最短路径表中,e,f的最短路径估值变小了,e在队列中存在,f不存在。
因此e不用入队了,f要入队,此时队列中的元素为d,e,f
队首元素d点出队,对以d为起始点的所有边的终点依次进行松弛操作(此处只有g这个点),此时路径表格状态为:
a
b
c
d
e
f
g
d[i]
0
24
8
15
15
11
19
在最短路径表中,g的最短路径估值变小了,g在队列中不存在,因此g要入队,此时队列中的元素为e,f,g
队首元素e点出队,对以e为起始点的所有边的终点依次进行松弛操作(此处只有g这个点),此时路径表格状态为:
a
b
c
d
e
f
g
d[i]
0
24
8
15
15
11
19
在最短路径表中,g的最短路径估值没有变小(松弛不成功),没有新结点入队,队列中元素为f,g
队首元素f点出队,对以f为起始点的所有边的终点依次进行松弛操作(此处有d,e,g三个点),此时路径表格状态为:
a
b
c
d
e
f
g
d[i]
0
24
8
15
13
11
14
在最短路径表中,e,g的最短路径估值又变小,队列中无e点,e入队,队列中存在g这个点,g不用入队,此时队列中元素为g,e
队首元素g点出队,对以g为起始点的所有边的终点依次进行松弛操作(此处只有b点),此时路径表格状态为:
a
b
c
d
e
f
g
d[i]
0
17
8
15
13
11
14
在最短路径表中,b的最短路径估值又变小,队列中无b点,b入队,此时队列中元素为e,b
队首元素e点出队,对以e为起始点的所有边的终点依次进行松弛操作(此处只有g这个点),此时路径表格状态为:
a
b
c
d
e
f
g
d[i]
0
17
8
15
13
11
14
在最短路径表中,g的最短路径估值没变化(松弛不成功),此时队列中元素为b
队首元素b点出队,对以b为起始点的所有边的终点依次进行松弛操作(此处只有e这个点),此时路径表格状态为:
a
b
c
d
e
f
g
d[i]
0
17
8
15
13
11
14
在最短路径表中,e的最短路径估值没变化(松弛不成功),此时队列为空了
最终a到g的最短路径为14
SPFA算法实现:
1.【邻接矩阵算法框架】
procedurespfa;
begin
fillchar(q,sizeof(q),0);h:
=0;t:
=0;//队列
fillchar(v,sizeof(v),false);//v[i]判断i是否在队列中
fori:
=1tondod[i]:
=maxint;//初始化最小值
inc(t);q[t]:
=1;v[1]:
=true;//点1入队并做标记
d[1]:
=0;//这里把1作为源点
whilenot(h=t)dobegin
x:
=q[(h+1)modn];h:
=(h+1)modn;v[x]:
=false;//出队
fori:
=1tondo//枚举x指向的点i
if(a[x,i]>0)and(d[x]+a[x,i] begin
d[i]:
=d[x]+a[x,i];//更新x指向点的最短路径长度
ifnot(v[i])thenbegin//如果i不在队列中,则将其放入队列,改进与之相关的点
t:
=(t+1)modn;q[t]:
=i;v[i]:
=true;
end;
end;
end;
end;
2.【用邻接表表示的SPFA的算法】标准SPFA过程
(以求某个结点s到某个结点t的最短路为例,稍加修改即为单源最短路)
Pascal语言代码
programspfaprg;
const
maxp=10000;{最大结点数}
var{变量定义}
p,c,s,t:
longint;{p,结点数;c,边数;s:
起点;t:
终点}
a,b:
array[1..maxp,0..maxp]oflongint;{a[x,y]存x,y之间边的权;b[x,c]存与x相连的第c个边的另一个结点y}
d:
array[1..maxp]ofinteger;{队列}
v:
array[1..maxp]ofboolean;{是否入队的标记}
dist:
array[1..maxp]oflongint;{到起点的最短路}
head,tail:
longint;{队首/队尾指针}
procedureinit;
vari,x,y,z:
longint;
begin
read(p,c);
fori:
=1tocdo
begin
readln(x,y,z);{x,y:
一条边的两个结点;z:
这条边的权值}
inc(b[x,0]);b[x,b[x,0]]:
=y;a[x,y]:
=z;{b[x,0]:
以x为一个结点的边的条数}
inc(b[y,0]);b[y,b[y,0]]:
=x;a[y,x]:
=z;
end;
readln(s,t);{读入起点与终点}
end;
procedurespfa(s:
longint);{SPFA}
vari,,j,now,sum:
longint;
begin
fillchar(d,sizeof(d),0);
fillchar(v,sizeof(v),false);
forj:
=1topdo
dist[j]:
=maxlongint;
dist[s]:
=0;v[s]:
=true;d[1]:
=s;{队列的初始状态,s为起点}
head:
=1;tail:
=1;
whilehead<=taildo{队列不空}
begin
now:
=d[head];{取队首元素}
fori:
=1tob[now,0]do
ifdist[b[now,i]]>dist[now]+a[now,b[now,i]]then
begin