图论 第四讲 经典算法Word下载.docx
《图论 第四讲 经典算法Word下载.docx》由会员分享,可在线阅读,更多相关《图论 第四讲 经典算法Word下载.docx(19页珍藏版)》请在冰豆网上搜索。
![图论 第四讲 经典算法Word下载.docx](https://file1.bdocx.com/fileroot1/2023-2/5/79697122-e9af-4c0a-bdec-fb17b018193a/79697122-e9af-4c0a-bdec-fb17b018193a1.gif)
begin
read(a[i,j]);
end;
{――――――读入图――――――――}
=2tondo
min:
=10000;
{设置最大值}
if(notv[i])and(a[1,i]<
>
0)and(a[1,i]<
min)then
找到距离最短的顶点
=a[1,i];
k:
=i;
end;
v[k]:
{设置以访问标
志}
更新最短权值
if(a[1,k]<
0)and(a[k,i]<
0)then
if(a[1,k]+a[k,i]<
a[1,i])or(a[1,i]=0)then
a[1,i]:
=a[1,k]+a[k,i];
writeln(a[1,i]);
{输出最短路径}
close(input);
close(output);
end.
BELLMAN算法
可以求负权的最短路径,可以判断有无负权回路。
对每一个顶点v,设置变量dis[v],意义是源点到v的最短路径。
对每一条边做v-1次松弛操作(不知道为什么叫这个名字)。
松弛操作:
对于边(u,v)如果dis[v]>
dis[u]+a[u,v],那么dis[v]=dis[u]+dis[u,v]。
programBELLMAN;
constmaxp=800;
{最大点数}
maxc=1450;
{最大边数}
max=300000;
typeedge=record{边}
a,b,d:
integer;
varpa:
array[1..maxp]ofinteger;
e:
array[1..maxc]ofedge;
{边表}
dis:
array[1..maxp]oflongint;
n,p,c,i,j,s:
tot,min:
longint;
flag:
boolean;
procedurerelax(a,b,d:
integer);
{松弛操作}
varz:
z:
=dis[a]+d;
ifz<
disthenbegin
=z;
=false;
fillchar(pa,sizeof(pa),0);
readln(p,c);
{C为边数p为点数}
fori:
=1tocdoreadln(e.a,e.b,e.d);
{a点和b点相通,长度为d}
s:
=1;
{单源最短的源}
=1topdodis[i]:
=max;
{初始化}
dis[s]:
=0;
主程序
repeat
=true;
=1tocdo
withedobegin
relax(a,b,d);
relax(b,a,d);
untilflag;
forI:
writeln(dis[i]);
SPFA算法
初始时将源加入队列.每次从队列中取出一个元素,并对所有与他相邻的点进行松弛,若某个相邻的点松弛成功,则将其入队.直到队列为空时算法结束.
SPFA算法的效率
时间复杂度一般认为是O(kE)
其中k是一个较大的常数,不好估计,但是可以看出SPFA算法效率应当是很高的
经验表明Dijkstra算法的堆优化要比SPFA快,但SPFA比普通的Dijkstra算法快.而SPFA算法可以处理负权的问题,而且比Dijkstra算法的堆优化的代码要容易实现,因此SPFA是一个很好的算法.
SPFA在处理稀疏图时效果比较不错在处理稠密图时可能没有Floyd好
programSPFA;
const
maxn=800;
{最大点}
maxq=maxn;
{最大边}
var
n,p,c:
map:
array[1..maxn,1..maxn]ofinteger;
{图}
array[1..maxn,1..maxn]oflongint;
{距离}
connect:
array[1..maxn,0..maxn]ofinteger;
{存图}
procedureinit;
i,x,y,distance:
初始化
fillchar(connect,sizeof(connect),0);
fillchar(map,sizeof(map),255);
readln(p,c);
{p为点C为边}
=1tocdo{读入}
readln(x,y,distance);
if(map[x,y]=1)or(distance<
map[x,y])then
map[x,y]:
=distance;
map[y,x]:
inc(connect[x,0]);
connect[x,connect[x,0]]:
=y;
inc(connect[y,0]);
connect[y,connect[y,0]]:
=x;
=1topdo
map[i,i]:
=0;
dis[i,i]:
procedurespfa(v0:
integer);
{vo源点}
i,now,open,close:
queue:
array[1..maxq]ofinteger;
{队列}
inqueue:
array[1..maxn]ofboolean;
{是否入队}
begin
fillchar(inqueue,sizeof(inqueue),0);
fillchar(queue,sizeof(queue),0);
fillchar(dis[v0],sizeof(dis[v0]),255);
dis[v0,v0]:
queue[1]:
=v0;
{vo入队}
inqueue[v0]:
open:
{队首}
close:
{队尾}
whileclose<
opendo
inc(open);
now:
=queue[open];
{出队}
inqueue[now]:
=false;
=1toconnect[now,0]do
if(dis[v0,connect[now,i]]=-1)or(dis[v0,now]+map[now,connect[now,i]]<
dis[v0,connect[now,i]])then{松弛操作}
dis[v0,connect[now,i]]:
=dis[v0,now]+map[now,connect[now,i]];
ifnotinqueue[connect[now,i]]then{入队}
inc(close);
queue[close]:
=connect[now,i];
inqueue[connect[now,i]]:
procedurework;
i,j:
=1tocowin[0]do
spfa(cowin[i]);
write(dis[i,j],'
'
writeln;
init;
work;
*弗洛伊德算法
*传递闭包。
也算是一种动态规划。
求任意两点最短路径。
枚举中间结点k,首结点i和尾结点j
programfloyd;
vara:
{存储图}
i,j,k,n:
if(i<
j)and(a[i,j]=0)thena[i,j]:
=maxint;
{数据初始化}
fork:
ifk<
ithen
if(k<
j)and(i<
j)and(a[i,j]>
a[i,k]+a[k,j])then
a[i,j]:
=a[i,k]+a[k,j];
writeln(任意两点最短路径);
对于floyd算法有很重要的应用。
在王建德的紫皮书上有传递闭包的很多用处。
在这里略过,但是一定要看一下,用传递闭包预处理数据是很有用,很重要的。
求拓扑序列
定义:
我自己不好说,看王建德的紫皮(P118)。
不懂定义就看不懂算法。
特点:
一个有向图结点的拓扑序列不是唯一的。
有环图不能拓扑排序。
求一个图的拓扑序列,判断有无回路。
欲处理时候很有用,有的搜索前很必要,比如《神经网络》
前提:
有向图
预处理数据的时候记下每一个点的入度,每次找出一个入度为0的点,删掉这个点,更新与它相邻的点的入度(入度减一)。
programtppv;
constmaxn=100;
array[1..maxn,1..maxn]ofbyte;
into:
array[1..maxn]ofbyte;
{每个结点的入度}
n,I,j,k:
byte;
{读入数据}
I,j:
assign(input,’input.in’);
assign(output,’output.out’);
readln(n);
fillchar(map,sizeof(map),0);
fillchar(into,sizeof(into),0);
whilenot(seekeof(input))do
readln(I,j);
map[I,j]:
inc(into[j]);
{有一条边连入,那么入度加1}
{输入数据}
j:
while(j<
=n)and(into[j]<
0)doinc(j);
{找到一个入度为0的点}
ifj<
=nthen{判断有无回路}
write(j,'
into[j]:
=255;
{删掉此点}
ifmap[j,k]=1thendec(into[k]);
{更新与j点连结的点的入度}
end
else
beginwriteln(‘存在回路,没有拓扑序列’);
halt;
end;
最小生成树
Prim算法
无向图或者强连通的有向图。
上图(B)和(C)都是(A)的生成树,生成树中各边的权值之和称为这棵生成树的代价,代价最小的生成树称为“最小代价生成树”。
已知所有边的权值,用最小的代价将所有的结点连起来。
设图的顶点集合V共有n个顶点,则算法如下:
1、设置一个顶点的集合S1和一个边的集合TE,S1和TE的初始状态均为空集;
2、选定图中的一个顶点K,从K开始生成最小代价生成树,将K加入到集合S1;
3、重复下列操作,直到选取了n-1条边:
选取一条权值最小的边(X,Y),其中X∈S1,not(Y∈S1)。
将顶点Y加入集合S1,边(X,Y)加入集合TE。
以上过程有些抽象……
下图给出了上图(A)的最小代价生成树的生成过程:
下面的程序代码是蓝皮书《数据结构与算法设计》的源程序。
对于Minclosd和closd数组的设计,可以参考此书,讲的还算明白。
programprim;
varminclosd,closd:
array[1..1000]ofinteger;
{设置minclosd,closd数组}
i,j,min,n,k:
{读入图注意数据的预处理}
minclosd[i]:
closd[i]:
{两数组初始化}
if(minclosd[i]<
min)and(minclosd[i]>
0)then
×
主要部分×
beginmin:
=minclosd[i];
=iend;
minclosd[k]:
if(a[k,i]<
minclosd[i])and(minclosd[i]<
beginminclosd[i]:
=a[k,i];
=k;
writeln(j,'
closd[j]);
Kruskal算法
也是求最小生成树。
设置一个b数组为已加入的顶点集合,每次找最小的一条边加入,直到所有的顶点加入集合。
与PRIM的不同:
Kruskal算法在森林中的两棵树之间添安全边;
Prim算法在单棵树上添安全边。
时间复杂度上要比PRIM低个常数,因为PRIM对点而KRUSKAL对边。
对于一个稠密的完全图,边要比点多。
相同点:
贪心,选择边权最小的安全边
优化:
并查集
programKruskal;
array[1..150,1..150]ofinteger;
b:
array[1..150]ofsetofbyte;
{一个集合}{最多只有255个,可以自己写数组}
i,j,k,l,n,min:
{=====================init====================================}
assign(input,'
assign(output,'
reset(input);
rewrite(output);
=1tondoread(a[i,j]);
readln;
{读入图}
{===========================find=============================}
procedurefind;
=1tondob[i]:
=[i];
if(a[i,j]>
0)and(a[i,j]<
min)and(not(jinb[i]))then
=a[i,j];
l:
=j;
{找到一条最小代价边,两结点是k和l}
b[k]:
=b[k]+b[l];
ifiinb[k]thenb[i]:
=b[k];
{更新已加入的边集合}
writeln(k,'
l);
untilb[1]=[1..n];
{==========================main=============================}
init;
find;
close(input);
close(output);
求图的的关键路径
此块内容我到现在也没有搞多清楚,大家一起看看,谁明白了交流一下(王建德紫皮书上有专门的一节)。
下面给出一种讲解。
关键路径问题
利用AOV网络,对其进行拓扑排序能对工程中活动的先后顺序作出安排。
但一个活动的完成总需要一定的时间,为了能估算出某个活动的开始时间,找出那些影响工程完成时间的最主要的活动,我们可以利用带权的有向网,图中的顶点表示一个活动结束(开始)的事件,图中的边表示活动,边上的权表示完成该活动所需要的时间。
这种用边表示活动的网络,称为AOE网络。
下图表示一个具有12个活动的AOE网络。
图中有8个顶点,分别表示事件(状态)0到7,其中,0表示开始状态,7表示结束状态;
边上的权表示完成该活动所需要的时间。
在实际中,AOE网络没有回路,存在唯一的入度为0的开始顶点,存在唯一的出度为0的结束顶点。
在AOE网络上,要研究的问题是完成整个工程至少需要多少时间,哪些活动是影响工程进度的关键。
在AOE网络中,有些活动可以并行地进行,所以,完成工程的最少时间是从开始顶点到结束顶点的最长路径的长度。
关键路径就是指从开始结点到完成结点的具有最大长度的路径。
关键路径上的活动称为关键活动。
关键路径的长度就是完成整个工程所需要的最短时间。
下面给出计算AOE网络的关键路径的数据结构和算法:
[数据结构]
adj[0..n-1,0..n-1]:
存放图的邻接矩阵;
est[0..n-1]:
est[i]存放事件i能够发生的最早时间,实际上是从开始顶点到i的最长路径的长度;
elt[0..n-1]:
elt[i]存放事件i能够发生的最迟时间,这是保证后续的事件j(i+1=<
j<
=n-1)能够按进度完成的保证,计算时,从n-1到i倒推,elt[i]等于est[n-1]减去顶点i到顶点n-1的最长路径的长度;
ast[n-1]:
ast[i]存放活动i能够发生的最早时间,如果活动i的边为<
j,k>
。
则ast[i]=elt[j];
alt[n-1]:
alt[i]存放活动i能够发生的最迟时间,如果活动i的边为<
,则alt[i]=elt[k]-adj[j,k];
[算法]
关键活