最短路径问题
最短路径问题的抽象
在网络中,求两个不同顶点之间的所有路径中,边的权值之和最小的那一条路径。
- 这条路径就是两点之间的最短路径
- 第一个顶点为源点
- 最后一个顶点为终点
问题分类
单源最短路径问题:从某固定源点出发,求其到所有其他顶点的最短路径。
- (有向)无权图
- (有向)有权图
多源最短路径问题:求任意两顶点之间的最短路径
无权图的单源最短路径算法
按照递增(非递减)的顺序找出到各个顶点的最短路,类似BFS。
void Unweighted(LGraph Graph, int dist[], int path[], Vertex S){ //dist[]和path[]全部初始化为-1
Queue Q;
Vertex V;
PtrToAdjVNode W;
Q=CreateQueue(Graph->Nv); //创建空队列
dist[S]=0; //S为源点
AddQ(Q,S);
while(!IsEmpty(Q)){
V=DeleteQ(Q);
for(W=Graph->G[V].FirstEdge;W;W=W->Next) //对V的每个邻接点W->AdjV
if(dist[W->AdjV]==-1){ //若W->AdjV未被访问过
dist[W->AdjV]=dist[V]+1; //S到W->AdjV的距离更新
path[W->AdjV]=V; //将V记录在S到W->AdjV的路径上
AddQ(Q,W->AdjV);
}
}
}
时间复杂度T=O(|V|+|E|)
有权图的单源最短路径算法
按照递增的顺序找出到各个顶点的最短路径,我们使用Dijkstra算法。
- Dijkstra算法
- 令S=
- 对任一未收录的顶点v,定义dist[v]为s到v的最短路径长度,但该路径仅经过S中的顶点。即路径{s->(vi)->v}的最小长度
- 若路径是按照递增(非递减)的顺序生成的,则
- 真正的最短路径必须只经过S中的顶点
- 每次从未收录的顶点中选一个dist最小的收录(贪心)
- 增加一个v进入S,可能影响另外一个w的dist值
- dist[w]=min
Vertex FindMinDist(MGraph Graph, int dist[], int collected[]){ //返回未被收录顶点中dist最小者
Vertex MinV,V;
int MinDist=INFINITY;
for(V=0;V<Graph->Nv;V++){
if(collected[V]==false&&dist[V]<MinDist){ //若V未被收录,且dist[V]更小
MinDist=dist[V]; //更新最小距离
MinV=V; //更新对应顶点
}
}
if(MinDist<INFINITY) //若找到最小dist
return MinV; //返回对应顶点
else
return ERROR;
}
bool Dijkstra(MGraph Graph, int dist[], int path[], Vertex S){
int collected[MaxVertexNum];
Vertex V,W;
for(V=0;V<Graph->Nv;V++){ //初始化,默认邻接矩阵中不存在的边用INFINITY表示
dist[V]=Graph->G[S][V];
if(dist[V]<INFINITY)
path[V]=S;
else
path[V]=-1;
collected[V]=false;
}
dist[S]=0; //源点S
collected[S]=true;
while(1){
V=FindMinDist(Graph, dist, collected); //找到未被收录顶点中dist最小者V
if(V==ERROR)
break;
collected[V]=true; //收录V
for(W=0;W<Graph->Nv;W++) //对图中的每个顶点W
if(collected[W]==false&&Graph->G[V][W]<INFINITY){ //若W未被收录且W是V的邻接点
if(Graph->G[V][W]<0) //若有副边,不能正确解决
return false;
if(dist[V]+Graph->G[V][W]<dist[W]){ //若收录V使得dist[W]变小
dist[W]=dist[V]+Graph->G[V][W]; //更新dist[W]
path[W]=V; //更新S到W的路径
}
}
}
return true;
}
多源最短路径算法
对于多源最短路径算法,有两种方法
- 方法1:直接将单源最短路径算法调用|V|遍
- T=O(|V|^3+|E|*|V|)
- 对于稀疏图效果比较好
- 方法2:Floyd算法
- T=O(|V|^3)
- 对于稠密图效果比较好
在这里我们介绍Floyd算法
- Floyd算法
- Dk[i][j]=路径{i->{l<=k}->j}的最小长度
- D0,D1,···,D|V|-1[i][j]即给出了i到j的真正最短距离
- 当Dk-1已经完成,递推到Dk时:
- 如果k不在最短路径{i->{l<=k}->j}里面,则Dk=Dk-1
- 如果k在最短路径{i->{l<=k}->j}里面,则该路径必定由两段最短路径组成:Dk[i][j]=Dk-1[i][k]+Dk-1[k][j]
bool Floyd(MGraph Graph, WeightType D[][MaxVertexNum], Vertex path[][MaxVertexNum]){
Vertex i,j,k;
for(i=0;i<Graph->Nv;i++) //初始化
for(j=0;j<Graph->Nv;j++){
D[i][j]=Graph->G[i][j];
path[i][j]=-1;
}
for(k=0;k<Graph->Nv;k++)
for(i=0;i<Graph->Nv;i++)
for(j=0;j<Graph->Nv;j++)
if(D[i][k]+D[k][j]<D[i][j]){
D[i][j]=D[i][k]+D[k][j];
if(i==j&&D[i][j]<0) //若发现负值圈
return false; //不能正确解决,返回错误
path[i][j]=k;
}
return true;
}