您的位置:首页 > 其它

笔试算法题(49):简介 - 图最短路径算法

2014-05-28 10:23 295 查看
图最短路径算法(Graph Shortest Path Algorithm, eg: Floyd-Warshall, Dijkstra, Bellman-Ford, SPFA, Kruskal, Prim, Johnson)

最短路径问题有多个衍生问题(并且每个衍生问题都涉及是否有负权边)
单源点最短路径

单终点最短路径

单对顶点最短路径

任意顶点间最短路径

Floyd-Warshall Algorithm

适用于多源,可有负权边的有向图的最短路径;时间复杂度为O(V^3),空间复杂度为O(V^2);

二维数组path[i][j]表示顶点i到顶点j之间的代价(初始化时由于没有探测他们之间的代价关系,所以为无限大);本算法采取的策略是穷举所有顶点对之间所有可能的中间顶点,并选择代价最小的作为最优解;

procedure FloydWarshallWithPathReconstruction ()
for k := 1 to n
for i := 1 to n
for j := 1 to n
if path[i][k] + path[k][j] < path[i][j] then
path[i][j] := path[i][k]+path[k][j];
next[i][j] := k; //next[i][j]表示顶点i和顶点j的中间点

procedure GetPath (i,j)
if path[i][j] equals infinity then
return "no path";
int intermediate := next[i][j];
if intermediate equals 'null' then
return " "; /* there is an edge from i to j, with no vertices between */
else
return GetPath(i,intermediate) + intermediate + GetPath(intermediate,j);


Dijkstra Algorithm

适用于有向、无负权边图中,单个源点到其他所有顶点的最短路径问题(Single-Source Shortest Path Problem for Graph with Non-Negative Edge Path Costs)。给定一个带权重的有向图G,V表示所有点的集合,E表示所有边的集合,并且每条边具有权重值w,要求找到给定start点到V中所有其他点 的最短距离。在寻路过程中,如果发现一个新的顶点M,并且从源点到这一个顶点M再到前一个目标顶点的距离比之前的距离小,则更新这个目标定点的最小距离;

Dijkstra算法适用于稠密图(边多点少),时间复杂度:使用最小优先队列实现Extract_Min()函数的话,为O(V^2 + E);使用二叉堆实现Extract_Min()函数的话,为O(V^2);使用斐波那契堆(Fibonacci Heap)实现Extract_Min()函数的话,为O(V*lgV + E);Dijkstra算法的一个应用是OSPF(Open Shortest Path First,开放最短路径优先),网络路由寻址的实现

function Dijkstra(G, w, s)
for each vertex v in V[G] //初始化
d[v] := infinity //d[v]存储其他顶点到起始点s的最短距离
previous[v] := undefined //previous[v]存储所有顶点的前置节点
d[s] := 0
S := empty set
Q := set of all vertices
while Q is not an empty set // Dijkstra演算法主體
u := Extract_Min(Q) //Extract_Min()一般使用最小堆实现O(logN)
S := S union {u}
for each edge (u,v) outgoing from u
if d[v] > d[u] + w(u,v) //根据当前节点u的d[u]更新其相邻节点的d[v]
d[v] := d[u] + w(u,v)
previous[v] := u


Bellman-Ford Algorithm

适用于单源、可有负权边的有向图的最短路径,Bellman-Ford对每个顶点都只进行一次处理,所以可以处理负权边,而Dijkstra由于是根据边权值大小选择下一条边,所以负权边可能造成循环;此算法时间复杂度为O(VE),空间复杂度为O(V);

数组V[k]表示所有顶点到source的最短距离(初始化为Infinite),并且使用数组P[h]记录所有顶点的前驱,用于记录最短路径;遍历所有 的边集合E内的边e,e连接顶点u和v,判断u和v之间是否可以组成最短路径,这样的遍历重复|V|次;由于V[k]中元素初始化为Infinite,所 以如果如果某个环内存在负权值边,则算法失败;

procedure BellmanFord(list vertices, list edges, vertex source)
// This implementation takes in a graph, represented as lists of vertices
// and edges, and modifies the vertices so that their distance and
// predecessor attributes store the shortest paths.

// Step 1: initialize graph
for each vertex v in vertices:
if v is source then v.distance := 0
else v.distance := infinity
v.predecessor := null

// Step 2: relax edges repeatedly
for i from 1 to size(vertices)-1:
for each edge uv in edges: // uv is the edge from u to v
u := uv.source
v := uv.destination
if u.distance + uv.weight < v.distance:
v.distance := u.distance + uv.weight
v.predecessor := u

// Step 3: check for negative-weight cycles
for each edge uv in edges:
u := uv.source
v := uv.destination
if u.distance + uv.weight < v.distance:
error "Graph contains a negative-weight cycle"


SPFA (Shortest Path Faster Algorithm)

适用于单源、可有负权边的有向图;大多数时候当图存在负权边,Dijkstra不能使用,而Bellman-Ford时间复杂度过高,所以最好使用 SPFA;其实SPFA是Bellman-Ford的优化版本,时间复杂度为O(kE),K是一个远小于V的数字,表示所有顶点进入FQ队列的平均次数 (<=2),但是SPFA及其不稳定,并严重依赖于图数据;

SPFA的策略是只有那些在前一次的Relax中的最小距离减小的点,才检查他们的邻接节点的最小距离是否也可减小。SPFA并不会在每一个顶点的最短路 径更新之后就去更新与其相连的顶点,而是等所有顶点都处理完全之后再进行最短路径的更新,这样大大减少重复更改最短路径的次数;所以SFPA中顶点会多次 进入队列,SPFA适合用于稀疏图,此时其拥有最高的效率;

数组D[k]存储所有其他顶点到Source的最短距离(初始化为Infinite);数组G[i][j]存储图中直接相连的顶点之间的距离;FIFO队 列Q存储需要进一步优化的顶点,每次从Q中取出顶点u,对每一个u直接相连的顶点v进行Relax操作,如果顶点v的最短距离改变了,并检查v没有在Q 中,则将v加入Q中,之后处理下一个与u直接相连的顶点;检查完u所有直接相连的顶点之后从Q中取出下一个顶点,并进行相同的处理;当Q为空的时候算法结 束,次数D[k]存储的就是图中每一个顶点到source的最短距离;

根据将顶点插入到FIFO队列Q的位置不同,SPFA可有两种优化:Small Label First (SLF)和Large Label Last (LLL);但由于SPFA及其不稳定,所以一般情况都使用Dijkstra替代;

void spfa(int start){
int i,j;
//初始化部分
for (i=1;i<=n;++i){
dist[i]=2147483647;
inqueue[i]=0;
}
//将头节点入队
dist[start]=0;
int h=0,t=1;
inqueue[start]=1;
queue[1]=start;
int now;
do{
h++;
now=Connect[queue[h];
inqueue[queue[h]=0;
while (now){
if (dist[Data[now].v]>dist[queue[h]+Data[now].w){
dist[Data[now].v]=dist[queue[h]+Data[now].w;
//进行松弛并扩展被松弛的点
if (!inqueue[Data[now].v]){
inqueue[Data[now].v]=1;
queue[++t]=Data[now].v;
}
}
now=Pre[now];
}
}while (h<t);
}


Johnson Algorithm

为了让Dijkstra适用于具有负权值边的图,Johnson通过特殊方式将负权值图转换为正权值图,并且为所有顶点之间的最短路径;

首先进行一次Bellman-Ford,然后利用等式W(i,j)=h[i]-h[j]+w(i,j);对原图进行重新标号(Re-wrighting, 其实是去除负权值边从而可以使用Dijkstra算法,h[]就是通过Bellman-Ford得到的路径标记,w[][]是边的原始权值);最后对每个 点使用Dijkstra。Johnson是目前在无负权值图中对所有点求最短路径的最高效的算法;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: