基础图论算法最短路径Word下载.docx
《基础图论算法最短路径Word下载.docx》由会员分享,可在线阅读,更多相关《基础图论算法最短路径Word下载.docx(44页珍藏版)》请在冰豆网上搜索。
readln(n);
fori:
=1tondo
begin
forj:
=1tondoread(a[i,j]);
readln;
end;
fillchar(flag,sizeof(flag),false);
flag[1]:
=true;
minn:
=1;
forx:
=2tondo
begin
min:
=32767;
if(a[1,i]<
min)and(flag[i]=false)then
=a[1,i];
=i;
flag[minn]:
forj:
if(j<
>
minn)and(a[1,minn]+a[minn,j]<
a[1,j])and(flag[j]=false)thena[1,j]:
=a[1,minn]+a[minn,j];
end;
=1tondo write(a[1,i],'
'
);
end.
4
010030999
10009910
3099099
99910990
程序输出:
010030110
2.一般的dijkstra算法(邻接矩阵表示+最短的具体路径):
programzudlouj;
constn=6;
max=10000;
cost:
array[1..6,1..6]ofreal=((0,50,10,max,45,max),(max,0,15,max,10,max),(20,max,0,15,max,max),(max,20,max,0,35,max),(max,max,max,30,0,max),(max,max,max,3,max,0));
{连通关系,连通路径长度,不连通则赋值为max}
vardist:
array[1..n]ofreal;
{起点到当前点的最短路径}
path,p:
array[1..n]of1..n;
{path[i]:
记录以i为终点的到i点能取得最短路径的起点}
first,tail,u:
1..n;
{起点与终点}
s:
setof1..n;
{存储已走过的点}
i,j,y,m:
min:
real;
begin
read(first,tail);
fori:
=1tondodist[i]:
=max;
{一开始所有的点都赋值为∞表示没有走过}
dist[first]:
=0;
{起点赋值为0}
s:
=[first];
{起点已走过,存入s中}
u:
=first;
{u表示当前子路径的起点位置}
whileu<
taildo{当子路径的起点不是终点,即还没走到终点时}
begin
for
j:
=1tondo
ifnot(jins)and(dist[u]+cost[u,j]<
dist[j])then{j表示子路径的终点,如果当前终点没有走过(不在s中)则看当前起点u到该点j的路径长度是否为到当前走到j的子路径中最短的,是则将到j的子路径的起点定位u(即path[j]:
=u),并且到j的最短路径dist[j]:
=dist[u]+cost[u,j]}
begindist[j]:
=dist[u]+cost[u,j];
path[j]:
=uend;
forj:
ifnot(jins)and(dist[j]<
min)thenbeginu:
=j;
min:
=dist[j];
end;
{找出当前子路径起点u所能到达的最短的j,并将j作为下一条子路径的起点u}
ifmin=max{如果min=max即当前点到四周所有的点都没有通路(死路一条了)}thenbeginwriteln('
Noanswer'
haltend;
=s+[u];
{将当前的子路径起点u放入s}
end;
{输出}
writeln('
mindist('
first,'
'
tail,'
)='
dist[tail]:
8:
2);
y:
=tail;
m:
while(y<
first)
do
begininc(m);
p[m]:
=y;
y:
=path[y];
{p[1]:
1为终点,p[m]为起点(从后到前寻找回路径)}
write('
path:
'
first);
forj:
=mdownto1do
write('
->
p[j]);
writeln;
end.
3.堆优化的dijkstra算法(dijkstra+邻接表+heap)
programSSSP;
{singlesourceshortestpath}
{假设一个图的最大节点数为1000,所有运算在integer范围内}
{程序目标:
给定有向图的邻接表,求出节点1到节点n的最短路径长度}
constmaxn=1000;
{最大节点数}
var
n:
{节点个数}
deg:
array[1..maxn]ofinteger;
{每个节点的度数}
list:
array[1..maxn,1..maxn,1..2]ofinteger;
{邻接表,第一个元素表示边的终点,第二个元素表示边的长度}
count:
{堆内元素个数计数器}
heap:
{heap[i]表示堆内的第i的元素的节点编号}
pos:
{表示编号为i的元素在堆内的位置}
key:
{表示节点1到节点i的最短距离}
exist:
array[1..maxn]ofboolean;
{表示节点i是否存在于堆中}
i,j,now:
procedureswap(vari,j:
integer);
{交换整数i和j}
vartemp:
temp:
i:
=temp;
procedureheapify(p:
{调整堆的过程,向下调整}
varbest:
best:
=p;
if(p*2<
=count)and(key[heap[p*2]]<
key[heap[best]])thenbest:
=p*2;
if(p*2+1<
=count)and(key[heap[p*2+1]]<
=p*2+1;
ifbest<
pthen
swap(pos[heap[p]],pos[heap[best]]);
swap(heap[p],heap[best]);
heapify(best);
proceduremodify(id,new_key:
{判断new_key与key[id]大小,并修改key[id]大小,向上调整}
varp:
if(new_key<
key[id])then
key[id]:
=new_key;
p:
=pos[id];
while(p>
1)and(key[heap[p]]<
key[heap[pdiv2]])do
swap(pos[heap[p]],pos[heap[pdiv2]]);
swap(heap[p],heap[pdiv2]);
=pdiv2;
procedureextract(varid,dis:
{读取堆中最小元素的节点编号和节点1到该节点的距离}
id:
=heap[1];
dis:
=key[id];
dec(count);
if(count>
0)then
swap(pos[heap[1]],pos[heap[count+1]]);
swap(heap[1],heap[count+1]);
heapify
(1);
{读取数据}
readln(n);
read(deg[i]);
=1todeg[i]doread(list[i,j,1],list[i,j,2]);
{初始化}
exist[i]:
pos[i]:
heap[i]:
key[i]:
=maxint;
=n;
key[1]:
{dijkstra算法的主要操作}
while(count>
0)do
extract(i,now);
ifnow=maxintthenbreak;
=1todeg[i]do
ifexist[list[i,j,1]]thenmodify(list[i,j,1],now+list[i,j,2]);
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]
∞
首先源点a入队,当队列非空时:
1、队首元素(a)出队,对以a为起始点的所有边的终点依次进行松弛操作(此处有b,c,d三个点),此时路径表格状态为:
24
8
15
在松弛时三个点的最短路径估值变小了,而这些点队列中都没有出现,这些点需要入队,此时,队列中新入队了三个结点b,c,d
队首元素b点出队,对以b为起始点的所有边的终点依次进行松弛操作(此处只有e点),此时路径表格状态为:
30
在最短路径表中,e的最短路径估值也变小了,e在队列中不存在,因此e也要入队,此时队列中的元素为c,d,e
队首元素c点出队,对以c为起始点的所有边的终点依次进行松弛操作(此处有e,f两个点),此时路径表格状态为:
11
在最短路径表中,e,f的最短路径估值变小了,e在队列中存在,f不存在。
因此e不用入队了,f要入队,此时队列中的元素为d,e,f
队首元素d点出队,对以d为起始点的所有边的终点依次进行松弛操作(此处只有g这个点),此时路径表格状态为:
19
在最短路径表中,g的最短路径估值变小了,g在队列中不存在,因此g要入队,此时队列中的元素为e,f,g
队首元素e点出队,对以e为起始点的所有边的终点依次进行松弛操作(此处只有g这个点),此时路径表格状态为:
在最短路径表中,g的最短路径估值没有变小(松弛不成功),没有新结点入队,队列中元素为f,g
队首元素f点出队,对以f为起始点的所有边的终点依次进行松弛操作(此处有d,e,g三个点),此时路径表格状态为:
13
14
在最短路径表中,e,g的最短路径估值又变小,队列中无e点,e入队,队列中存在g这个点,g不用入队,此时队列中元素为g,e
队首元素g点出队,对以g为起始点的所有边的终点依次进行松弛操作(此处只有b点),此时路径表格状态为:
17
在最短路径表中,b的最短路径估值又变小,队列中无b点,b入队,此时队列中元素为e,b
在最短路径表中,g的最短路径估值没变化(松弛不成功),此时队列中元素为b
队首元素b点出队,对以b为起始点的所有边的终点依次进行松弛操作(此处只有e这个点),此时路径表格状态为:
在最短路径表中,e的最短路径估值没变化(松弛不成功),此时队列为空了
最终a到g的最短路径为14
SPFA算法实现:
1.【邻接矩阵算法框架】
procedurespfa;
fillchar(q,sizeof(q),0);
h:
t:
//队列
fillchar(v,sizeof(v),false);
//v[i]判断i是否在队列中
fori:
=1tondod[i]:
//初始化最小值
inc(t);
q[t]:
v[1]:
//点1入队并做标记
d[1]:
//这里把1作为源点
whilenot(h=t)dobegin
x:
=q[(h+1)modn];
=(h+1)modn;
v[x]:
=false;
//出队
=1tondo//枚举x指向的点i
if(a[x,i]>
0)and(d[x]+a[x,i]<
d[i])then
begin
d[i]:
=d[x]+a[x,i];
//更新x指向点的最短路径长度
ifnot(v[i])thenbegin//如果i不在队列中,则将其放入队列,改进与之相关的点
=(t+1)modn;
v[i]:
2.【用邻接表表示的SPFA的算法】标准SPFA过程
(以求某个结点s到某个结点t的最短路为例,稍加修改即为单源最短路)
Pascal语言代码
programspfaprg;
const
maxp=10000;
{最大结点数}
var{变量定义}
p,c,s,t:
longint;
{p,结点数;
c,边数;
起点;
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:
{队首/队尾指针}
procedureinit;
vari,x,y,z:
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]:
end;
readln(s,t);
{读入起点与终点}
procedurespfa(s:
longint);
{SPFA}
vari,,j,now,sum:
fillchar(d,sizeof(d),0);
forj:
=1topdo
dist[j]:
=maxlongint;
dist[s]:
=0;
v[s]:
=true;
d[1]:
=s;
{队列的初始状态,s为起点}
head:
=1;
tail:
whilehead<
=taildo{队列不空}
now:
=d[head];
{取队首元素}
fori:
=1tob[now,0]do
ifdist[b[now,i]]>
dist[now]+a[now,b[now,i]]then