(三) 图的最短路径问题
1. 问题分类
(1)单源最短路径问题:从固定源点出发,求其到所有其他顶点的最短路径
- 无权图
- 有权图
(2)多源最短路径问题:从固定源点出发,求任意两顶点间的最短路径
2. 无权图的最短路径算法
-
按照递增(非递减)的顺序找出源点到各个顶点的最短路。
-
思想类似于BFS
[外链图片转存失败(img-CKQDy8zU-1567356507444)(C:\Users\alway\AppData\Roaming\Typora\typora-user-images\1567349652122.png)]
void Unweighted(Vertex s) {
// visited[v] = true;
Enqueue(s, Q);
while(!isEmpty(Q)) {
v = Dequeue(Q);
for (v的每个邻接点w) {
if(dist[w]!=-1) { // dist初始化为-1, dist[v] = 0
// visited[v] = true;
dist[w] = dist[v] + 1;
path[w] = v; // path 初始化为-1, s到w的路上经过v
Enqueue(w, Q);
}
}
}
}
3. 有权图的最短路径算法-----Dijkstra算法
-
按递增的顺序找出源点到各个顶点的最短路
算法:
-
令S={源点 s + 已经确定了最短路径的顶点 v i v_i vi}
-
对任一未收录的顶点v,定义dist[v]为 s 到 v 的最短路径长度,但该路径仅经过S中的顶点。即 { s − > ( v i ∈ S ) − > v } \{s->(v_i\in S)->v\} {s−>(vi∈S)−>v}的最小长度。
-
若路径是按递增(非递减)的顺序生成的,则:
- 真正的最短路必须只经过S中的顶点(反证法可验证)
- 每次从未收录的顶点中选一个dist最小的收录(贪心)
- 增加一个 v 进到 S,可能影响另外一个w的dist值!(只影响那些与v直接相连的顶点)
- dist[w] = min(dist[w], dist[v] + <v, w>)
void Dijkstra(Vertex s) {
while (1) {
v = 未收录顶点中的dist最小的顶点;
if(满足条件的v不存在) {
break;
}
collected[v] = true;
for (v的每个邻接点w) {
if(!collected[w])
if(dist[w] + <v, w> < dist[w]) {
dist[w] = dist[w] + <v, w>;
path[w] = v;
}
}
}
}
// 不能解决有负边的情况,dist[w] + <v, w> < dist[v]....
1)直接扫描所有未收录顶点---- O ( ∣ V ∣ ) O(|V|) O(∣V∣)
- Dijkstra算法时间复杂度 T = O ( ∣ V ∣ 2 + ∣ E ∣ ) T=O(|V|^2+|E|) T=O(∣V∣2+∣E∣)
- 对于稠密图效果好
2)将dist存在最小堆中---- O ( l o g ∣ v ∣ ) O(log|v|) O(log∣v∣) - 更新dist[w]— O ( l o g ∣ V ∣ ) O(log|V|) O(log∣V∣)
- Dijkstra算法时间复杂度 T = O ( ∣ V ∣ l o g ∣ V ∣ + ∣ E ∣ l o g ∣ V ∣ ) = O ( ∣ E ∣ l o g ∣ V ∣ ) T=O(|V|log|V|+|E|log|V|)=O(|E|log|V|) T=O(∣V∣log∣V∣+∣E∣log∣V∣)=O(∣E∣log∣V∣)
- 对于稀疏图效果好
3. 多源最短路径算法-----Floyd算法
- 直接将单源最短路算法调用 |V| 遍
- T = O ( ∣ V ∣ 3 + ∣ E ∣ × ∣ V ∣ ) T=O(|V|^3+|E|\times|V|) T=O(∣V∣3+∣E∣×∣V∣)
- 对于稀疏图效果好
- Floyd算法
- T = O ( ∣ V ∣ 3 ) T=O(|V|^3) T=O(∣V∣3)
- 对于稠密图效果好
算法:
- D k [ i ] [ j ] D^k[i][j] Dk[i][j]=路径 { i − > { l < = k } − > j } \{i->\{l<=k\}->j\} {i−>{l<=k}−>j}的最小长度
- D 0 , D 1 , . . . , D ∣ V ∣ − 1 [ i ] [ j ] D^0, D^1, ..., D^{|V|-1}[i][j] D0,D1,...,D∣V∣−1[i][j]即给出了 i 到 j 的真正最短距离
- 从
D
k
−
1
D^{k-1}
Dk−1递推到
D
k
D^k
Dk:
- 或者 k ∉ k\notin k∈/最短路径 { i − > { l < = k } − > j } \{i->\{l<=k\}->j\} {i−>{l<=k}−>j},则 D k = D k − 1 D^k=D^{k-1} Dk=Dk−1
- 或者
k
∈
k\in
k∈最短路径
{
i
−
>
{
l
<
=
k
}
−
>
j
}
\{i->\{l<=k\}->j\}
{i−>{l<=k}−>j},则该路径必定由两段最短路径组成:
- D k [ i ] [ j ] = D k − 1 [ i ] [ k ] + D k − 1 [ k ] [ j ] D^k[i][j]=D^{k-1}[i][k]+D^{k-1}[k][j] Dk[i][j]=Dk−1[i][k]+Dk−1[k][j]
void Floyd() {
for (i = 0; i < N; i++) {
for (j = 0; j < N; j++) {
D[i][j] = G[i][j];
path[i][j] = -1;
}
}
for (k = 0; k < N; k++) {
for (i = 0; i < N; i++) {
for (j = 0; j < N; j++) {
if (D[i][k] + D[k][j] < D[i][j]) {
D[i][j] = D[i][k] + D[k][j];
path[i][j] = k;
}
}
}
}
}
4. 代码实现
/* 邻接表存储 - 无权图的单源最短路算法 */
/* dist[]和path[]全部初始化为-1 */
void Unweighted ( LGraph Graph, int dist[], int path[], Vertex S )
{
Queue Q;
Vertex V;
PtrToAdjVNode W;
Q = CreateQueue( Graph->Nv ); /* 创建空队列, MaxSize为外部定义的常数 */
dist[S] = 0; /* 初始化源点 */
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; /* W->AdjV到S的距离更新 */
path[W->AdjV] = V; /* 将V记录在S到W->AdjV的路径上 */
AddQ(Q, W->AdjV);
}
} /* while结束*/
}
/* 邻接矩阵存储 - 有权图的单源最短路算法 */
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;
/* 初始化:此处默认邻接矩阵中不存在的边用INFINITY表示 */
for ( V=0; V<Graph->Nv; V++ ) {
dist[V] = Graph->G[S][V];
if ( dist[V]<INFINITY )
path[V] = S;
else
path[V] = -1;
collected[V] = false;
}
/* 先将起点收入集合 */
dist[S] = 0;
collected[S] = true;
while (1) {
/* V = 未被收录顶点中dist最小者 */
V = FindMinDist( Graph, dist, collected );
if ( V==ERROR ) /* 若这样的V不存在 */
break; /* 算法结束 */
collected[V] = true; /* 收录V */
for( W=0; W<Graph->Nv; W++ ) /* 对图中的每个顶点W */
/* 若W是V的邻接点并且未被收录 */
if ( collected[W]==false && Graph->G[V][W]<INFINITY ) {
if ( Graph->G[V][W]<0 ) /* 若有负边 */
return false; /* 不能正确解决,返回错误标记 */
/* 若收录V使得dist[W]变小 */
if ( dist[V]+Graph->G[V][W] < dist[W] ) {
dist[W] = dist[V]+Graph->G[V][W]; /* 更新dist[W] */
path[W] = V; /* 更新S到W的路径 */
}
}
} /* while结束*/
return true; /* 算法执行完毕,返回正确标记 */
}
/* 邻接矩阵存储 - 多源最短路算法 */
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; /* 算法执行完毕,返回正确标记 */
}