【最短问题】
最短路径问题:如果从图中某一顶点(称为源点)到达另一顶点(称为终点)的路径可能不止一条,如何找到一条路径,使得沿此路径各边上的权值总和达到最小。(权值非负)
【Dijkstra思想】
按路径长度的递增次序,逐步产生最短路径的算法。首先求出长度最短的一条最短路径(顶点0到其他顶点的直接路径最短的路径),再参照它求出长度次短的一条最短路径,依次类推,直到从顶点v到其它各顶点的最短路径全部求出为止。
【步骤】
注意:
V0到T中顶点Vk的最短路径,要么是从v0到vk的直接路径;要么是从v0经S[Ii]=1中某个顶点 vi 再到vk的路径。
需要:
dist[n]:dist[i]表示当前找到的从源点v0到终点vi的最短路径的长度,初始状态下,dist[i]为E[v0][i],即邻接矩阵的第v0行。
S[n]:为0表示vi还未加入到集合S中,为1表示vi已加入到集合S中。初始状态下,S[v0]为1,其余为0,最初只有顶点v0。
path[n]:p[i]表示在最短路径上顶点vi的前一个顶点号,p[i]=k。从k到i。
过程:
①在dist[ ]里查找S[i]!=1,并且dist[i]最小的顶点u
②将S[u]改为1,表示顶点u已经加入进来了。
③修改其它顶点的dist[ ]及path[ ]。当S[k]!=1,且顶点vu到顶点vk有边(E[u][k]<MAX),且dist[u]+map[u][k]<dist[k],则修改dist[k]为dist[u]+E[u][k],修改path[k]为u。
现在解释一下从初始状态到(1):初始状态。要从未加入S集合的dist[]开始找,可知dist[2]=5最小,记下u=2,那么把点2加入S集合,S[2]=1,接着修改dist[i]和path[i],dist[i]=min{ dist[i],dist[u] + map[2][i] }={0,20,5,30,∞,12},20是因为0到2为5,2到1为15,所以0到1为20,比原来∞小,所以用20去更新。以此类推。
可知 第一条 最短路径为 0->2 第二条为0->2->5.
【应用】
求从源点0到终点4的最短路径和最短路径长度。
path[4]=1→path[1]=2→path[2]=0 dist[4]=28
最短路径为0,2,1,4,最短路径长度为dist[4]=28。
【模板代码】
#include <stdio.h>
#include <string.h>
using namespace std;
#define INF 0x3f3f3f3f //无穷大
#define MAXN 20 //顶点个数的最大值
int n; //顶点个数
int Edge[MAXN][MAXN]; //邻接矩阵
int S[MAXN]; //Dijkstra算法用到的3个数组
int dist[MAXN]; //
int path[MAXN]; //
//求顶点v0到其他顶点的最短路径
void Dijkstra( int v0 )
{
for( int i=0; i<n; i++ ) // 初始化三个数组
{
dist[i] = Edge[v0][i];
S[i] = 0;
if( i!=v0 && dist[i]<INF )
path[i] = v0;
else
path[i] = -1;
}
//顶点v0加入到顶点集合S,然后v0到v0的距离为0.
S[v0] = 1;
dist[v0] = 0;
for( int i=0; i<n-1; i++ ) //从顶点v0确定n-1条最短路径
{
int min = INF;
int u = v0;
//选择当前未加入集合S中具有最短路径的顶点u
for(int j=0; j<n; j++ )
{
if( !S[j] && dist[j]<min )
{
u=j;
min = dist[j]; //最短路径记为min
}
}
S[u] = 1; //将顶点u加入到集合S,表示它的最短路径已求得
for( int k=0; k<n; k++ ) //修改dist和path数组元素值
{
//未加入集合S中的顶点 // Edge[u][k]<INF代表点u可直达k //经过这个点从0到终点的距离小于原来不经过这个点到终点距离
if( !S[k] && Edge[u][k]<INF && dist[u] + Edge[u][k] < dist[k] )
{
dist[k] = dist[u] + Edge[u][k]; path[k] = u;
}
}
}
}
int main( )
{
int u, v, w; //边的起点和终点及权值
scanf( "%d", &n ); //读入顶点个数n
while (1)
{
scanf("%d%d%d", &u, &v, &w); //读入边的起点和终点
if (u == -1 && v == -1 && w == -1)
break;
Edge[u][v] = w; //构造邻接矩阵
}
for( int i=0; i<n; i++ )
{
for( int j=0; j<n; j++ )
{
if( i==j ) Edge[i][j] = 0; //自己到自己为0
else if( Edge[i][j]==0 ) //不存在i直接到j
Edge[i][j] = INF;
}
}
Dijkstra( 0 ); //求顶点0到其他顶点的最短路径
int shortest[MAXN]; //存放最短路径上的各个顶点的序号
for( int i=1; i<n; i++ )
{
printf( "%d\t", dist[i] ); //输出顶点0到顶点i的最短路径长度
//以下代码用于输出顶点0到顶点i的最短路径
memset( shortest, 0, sizeof(shortest) );
int k = 0; //k表示shortest数组中最后一个元素的下标
shortest[k] = i; //表示从i要到0.
while( path[ shortest[k] ] != 0 ) //path[i]!=0代表i前面还有点。直到它前面这个点的path为0
{
k++;
shortest[k] = path[ shortest[k-1] ];
}
k++;
shortest[k] = 0; //最后把0补上
for( int j=k; j>0; j-- )
printf( "%d→", shortest[j] );
printf( "%d\n", shortest[0] );
}
return 0;
}