最短路径问题的抽象:
在网络中,求两个不同顶点之间的所有路径中边的权值之和最小的一条路径:
(1)这条路径就是这两个顶点间的最短路径(Shortest Path);
(2)第一个顶点为源点(Source);
(3)最后一个顶点为终点(Destination)。
(4)不考虑负值圈(negative-cost-cycle)情况。
问题分类:
(1)单源最短路径问题:从固定源点出发,求其到其它所有顶点的最短路径(分为有向无权图和有向有权图两种情况);
(2)多源最短路径问题:求任意两顶点间的最短路径。
(1)单源最短路径——无权图:
按照递增的顺序找出到各个顶点的最短路(从这里可以看出复合BFS的理念,由近到远得去遍历图中的顶点)
const int INF = ( 1 << 30 - 1 ) //定义无穷大为2^30 - 1
int visited[MaxVertexNum]; //访问标记数组
dist[MaxVertexNum]; //距离数组,记录源点到某一个顶点的最短距离
path[MaxVertexNum]; //记录源点到某一个顶点路径上的经过的顶点
void BFSTraverse_Min_Distance( G ptrg ); //单源最短路径无权图遍历
void BFS_Min_Distance( G ptrg, int v ); //单源最短路径问题——无权图
void BFSTraverse_Min_Distance( G ptrg )
{
int i;
for( i = 0; i < ptrg -> vernum; i++ ){
visited[i] = 0;
path[i] = -1;
}
InitQueue( Q );
}
void BFS_Min_Distance( G ptrg, int v )
{
int i;
for( i = 0; i < ptrg -> vernum; i++ )
dist[i] = INF;
visited[v] = 1;
dist[v] = 0;
EnQueue( Q, v );
while( !IsEmpty( Q ) ){
DeQueue( Q, v );
for( i = FirstNeighbor( G, v ); i >= 0; i = NextNeighbor( G, v, i ) )
if( visited[i] == 0 ){
visited[i] = 1;
dist[i] = dist[v] + 1;
path[i] = v;
Enqueue( Q, i );
}
}
}
(2)单源最短路径——有权图(Dijkstra算法):
按照递增的顺序找出各个顶点的最短路。
Dijkstra算法:
(1)用一个集合S记录已求得最短路径的顶点,初始时把源点放进去;
(2)对任一未收录的顶点v,dist[v]表示源点到该顶点当前的最短距离(但该最短距离对应的路径仅经过S集合中的顶点);
(3)对顶点v,path[v]表示源点到该顶点最短路径上顶点v的前驱顶点,在算法结束后,就可以根据path的值追溯得到源点到顶点v的最短路径;
(4)注意:该最短路径是按照非递减的顺序生成的;
(5)每次从未收录的顶点中选一个dist值最小的顶点收录到集合S中(贪心策略);
(6)每在集合S中收录一个顶点时,可能会影响未收录进来顶点的dist值(该顶点是刚收录进来的顶点的邻接顶点)。
算法步骤:
(1)初始化:集合S使用数组存储,初始只有源点。dist数组初值设为源点到各顶点的直接距离,如果之间没有路径就设置为无穷大INF,path数组都设为-1;
(2)从V-S中选出一个源点到所有顶点路径长度中最短的,收录进S中,该顶点就是当前所找到的一条最短路径;
(3)然后修改未收录进各顶点的dist值(由于上一个收录进来的顶点可能影响他的邻接点的dist值);
(4)重复上述操作,直至把所有的顶点都收录进S集合中。
const int INF = ( 1 << 30 - 1 ) //定义无穷大为2^30 - 1
int S[MaxVertexNum]; //S集合
int V[MaxVertexNum]; //V集合
dist[MaxVertexNum]; //距离数组,记录源点到某一个顶点的最短距离
path[MaxVertexNum]; //记录源点到某一个顶点路径上的经过的顶点
void Dijkstra( G ptrg, int v ); //Dijkstra算法
void Dijkstra( G ptrg, int s )
{
int i, v;
for( i = 0; i < ptrg -> vernum; i++ ){
path[i] = -1;
dist[i] = INF;
S[i] = 0;
V[i] = 1;
}
while( 1 ){
for( i = 0; i < ptrg -> vernum; i++ )
if( s[0] == 0 )
break;
if( i == ptrg -> vernum )
break;
int min = -1;
for( i = 0; i < pyrg -> vernum; i++ )
if( V[i] == 1 && dist[i] < min )
v = i;
S[v] = 1;
V[v] = 0;
for( i = FirstNeighbor( G, v ); i >= 0; i = NextNeighbor( G, v, i ) )
if( S[i] == 0 ){
if( dist[v] + ptrg -> Edge[v][i] < dist [i] ){
dist [i] = dist[v] + ptrg -> Edge[v][i];
path[i] = v;
}
}
}
}
时间复杂度:
(1)邻接矩阵表示法:O(|V|^2);
(2)邻接表表示法:O(|V|^2)。
可以选择将dist值存储在最小堆中。
(3)多源最短路径问题:
方法一:直接将单源最短路径算法调用|V|遍,时间复杂度是:O(|V|^3 + |E| * |V|);
方法二:Floyd算法,时间复杂度是O(|V|^3)。
从上面可以看出来,当图为稀疏图时,方法一中时间复杂度的尾巴很小,所以两种算法都可以,但当图为稠密图时,方法一的尾巴就比较大,此时使用方法二会比较好。
Floyd算法:
递推产生一个n阶方阵D(-1),D(0),D(1),……D(|V| -1)。
(1)刚开始D(-1)即为原所给图的邻接矩阵,其中有边相连的元素对应的矩阵元素值用边的权值表示,如果没有边直接相连的,用无穷大INF表示;
(2)同样需要一个顶点集S,刚开始为空,则V-S集合=V;
(3)从V-S中依次选取顶点收录进S中作为中间顶点,然后检查其它顶点在加入中间顶点之后,到其它顶点的路径长度有没有减小,如果有减小,就更新这个距离值,所以从这可以看出D(k)[ i ] [ j ]的值就是从顶点i 到顶点j且中间顶点序号不大于k的最短路径长度;
(4)重复上述过程,直到所有顶点都收录进S中,最终得到的D(|V| - 1)就是多源最短路径问题的解。
Graph D[MaxVertexNum][MaxVertexNum]; //递推矩阵
path[MaxVertexNum][MaxVertexNum]; //记录对应顶点的前驱顶点序号
void Floyd( G ptrg ); //Floyd算法
void Floyd( G ptrg )
{
int i, j, k;
for( i = 0; i < ptrg -> vernum; i++ )
for( j = 0; j < ptrg -> vernum; j++ )
D[i][j] = ptrg -> Edge[i][j]; //D(-1)
for( k = 0; k < ptrg -> vernum; k++ )
for( i = 0; i < ptrg -> vernum; i++ )
for( j = 0; j < ptrg -> vernum; j++ )
if( D[i][k] + D[k][j] < D[i][j] ){ //加入中间顶点后如果如果最短路径长度值变小
D[i][j] = D[i][k] + D[k][j]; //更新最短路径值
path[i][j] = k; //把该中间顶点记成前驱
}
}