5.4 图的应用
图的应用主要包括:最小生成树、最短路径、拓扑排序和关键路径
5.4.1 最小生成树(MST)
连通图的生成树:图的极小连通子图,包含图中所有顶点,只含尽可能少的边。若砍去最小生成树的一条边,使生成树变成非连通图,加上一条边,会形成一条回路。
最小生成树:边权值最小的那棵生成树
-
最小生成树不唯一,但权值唯一,且是最小的。
-
算法:prim算法/kruskal算法,都是基于贪心算法
-
prim算法:
-
假设N={V,E}是连通网,ET是N上最小生成树边的集合。算法从VT-{u0},ET={}开始,重复执行:在所有u∈VT,v∈V-VT的边(u0,v0)∈E中找一条权值最小的边,将此边加入ET,将v0加入VT,知道VT=V为止。
-
该算法时间复杂度O(|v2|),不依赖E,适用于求解边稠密的图的最小生成树
-
-
kruskal算法:
-
初始化:VT=V,ET=∅,每个顶点构成一棵独立的树
-
循环:按G的边权递增顺序依次从E-ET选一条边,若加入边后不构成回路,则加入ET,否则舍弃。
-
通常采用堆来存放边的集合,每次选择最小权值的边只需要O(log|E|),可以采用并查集的数据结构表示T,构造T的复杂度为O(|E|log|E|)
-
适合边稀疏,顶点较多的图
-
5.4.2 最短路径
带权路径长度最短的路径称最短路径。求解最短路径的算法通常依赖一性质:两点之间的最短路径也包含了路径上其他顶点间的最短路径。一般分单源最短路径(某点到其他点的最短路径)和每对顶点间的最短路径。
问题:对任意给出的图G(V,E)和起点s,终点t,如何求S到T的最短路径。
解决最短路径问题的常用算法有 Dijkstra算法、Bellman-Ford算法、SPFA算法和Floyd算法。
1. Dijkstra算法
思想:对图G设定集合S,存放已经访问过的集合。然后从V-S中选择与起点s距离最短的顶点记为u,访问并加入集合S。之后,令顶点u为中介点,优化起点s与所有从u出发能到达的顶点v之间的最短距离。执行n次,直到集合S包含所有顶点。
算法具体实现:
(1)集合S可以用一个bool数组vis[]表示,当vis[i]=true时表示顶点Vi已被访问。
(2)令int型数组d[]表示起点s到达顶点Vi的最短距离,起始时除了起点s的d[s]=0,其余顶点都赋为一个很大的数。
Dijkstra也基于贪心策略,时间复杂度O(|V2|),若要求解每对顶点之间的最短距离,需要对每个顶点运行一次dijkstra算法,复杂度O(|V3|)
注:Dijkstra只能应对所有边权都是非负数的情况,如果边权出现负数,那么Dijkstra算法可能会出错。
//伪代码
Dijkstra(G,d[],s){
初始化;
for(循环n次){
u = 使d[u]最小的还未被访问的顶点的标号;
记u被访问;
for(从u出发能到达的所有顶点v){
if(v未被访问&&以u为中介使s到顶点v的最短距离d[v]更优){
优化d[v];
}
}
}
2. Bellman-Ford算法和SPFA算法
Bellman-Ford算法(BF算法)可解决单源最短路径问题,但能处理有负边权的情况。
现在考虑环,从某个顶底出发,经过若干不同的顶点之后可以回到该顶点的情况。根据环中边的边权之和的正负分零环、正环、负环。零环和正环不会影响最短路径的求解;而如果有负环,且从源点可以到达,就会影响求解,但如果负环无法从源点出发到达,则最短路径的求解不会受到影响。
思想:BF算法设置一个数组d,存放从源点到达各个顶点的最短距离。同时BF算法返回一个bool值:若存在从源点可到达的负环,返回false;否则返回true,此时数组d存放的值就是从源点到达各顶点的最短距离。
具体实现:
对图中边进行V-1轮操作,每轮都遍历图中所有的边:对每条边u->v,若以u为中介点能使d[v]更小,就更新。
for(i = 0; i < n-1; i++){
for(each edge u—>v){
if(d[u]+length[u->v] < d[v]){
d[v] = d[u] + length[u->v];
}
}
}
此时,若无从源点可到达的负环,则数组d所有值都达到了最优。因此,只需再对所有边进行一轮操作,判断是否有某条边u->v仍然可以更新,若有,则说明图中有从源点可达的负环,返回false;否则,说明d数组已达到最优。
BF算法时间复杂度为O(VE)
虽然BF算法思路简洁,但时间复杂度很高。BF算法每轮操作都操作所有的边,显然有很多无意义的操作。注意到,只有当某个顶点u的d[u]值改变时,从它出发的边的邻接点v的d[v]值才有可能改变。由此可以进行优化:建立一个队列,每次取出队首u,对从u出发的边进行松弛。如果可松弛则进行覆盖。如果v不在队列,则加入队列。这样操作直到队空(说明无从源点可达的负环),或某个顶点的入队次数超过v-1,说明有可达负环。这样优化后的算法叫做SPFA算法。它期望的时间复杂度是O(kE)
queue<int> Q;
源点s入队;
while(队非空){
取出队首元素u;
for(u的所有邻接边u->v){
if(d[u]+dis<d[v]){
d[v] = d[u]+dis;
if(v不在Q){
v入队;
if(v入队次数大于n-1){
说明有可达负环,return;
}
}
}
}
}
3. Floyd算法
解决全源最短路问题。时间复杂度O(n^3),限制顶点数n在200以内。
枚举顶点k∈[1,n]
以顶点k为中介点,枚举所有顶点对i和j(i∈[1,n],j∈[1,n])
如果 dis[i][k]+dis[k][j] < dis[i][j]成立
赋值 dis[i][j] = dis[i][k]+dis[k][j]
5.4.3 拓扑排序
有向无环图:若一个有向图中不存在环,称DAG图
AOV网:DAG表示一个工程,顶点表示活动,有向边<vi, vj>表示活动vi必须先于vj进行。称顶点表示活动的网络,记为AOV网,拓扑排序复杂度O(|V|+|E|)
AOE网:在带权有向图中,以顶点表示事件,有向边表示活动,边上的权值表示完成该活动所需开销。
最大路径:源点到汇点的所有路径中,具有最大路径长度的路径称关键路径,关键路径上的活动称关键活动。