数据结构 第五章 图——最短路径问题

最短路径问题的抽象:
在网络中,求两个不同顶点之间的所有路径中边的权值之和最小的一条路径:
(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;                   //把该中间顶点记成前驱 
				}
} 

你可能感兴趣的:(数据结构与算法分析)