1. 无权图的最短路径
对无权图,若从一顶点到另一顶点存在着一条路径,则称该路径长度为该路径上所经过的边的数目,它等于该路径上的顶点数减1。
图1-无权图的最短路径
比如:从顶点0到顶点5存在着一条路径,经过了3条边,因此顶点0到顶点5的路径长度为3,同时我们还知道这条路径有4个顶点(0,1,2,5),也就是说这条路径长度等于4 - 1。
由于从一顶点到另一顶点可能存在着多条路径,每条路径上所经过的边数可能不同,即路径长度不同,我们把路径长度最短(即经过的边数最少)的那条路径叫做最短路径
,其路径长度叫做最短路径长度或最短距离
。
比如:从顶点0到顶点5有两条路径,而通过对比路径1和路径2发现,路径1是最短路径,而路径1的路径长度为最短路径长度,也称为最短距离。
2. 带权图的最短路径
对于带权的图,考虑路径上各边上的权值,则通常把一条路径上所经边的权值之和定义为该路径的路径长度或称带权路径长度
。
图2-带权图的最短路径
从源点到终点可能不止一条路径,把带权路径长度最短的那条路径称为最短路径
,其路径长度(权值之和)称为最短路径长度或者最短距离
。
从上我们可以知道,带权图和无权图的最短路径来说,它们都有各自衡量最短路径的标准。
3. Dijkstra算法
问题:给定一个带权有向图G与源点v,求从v到G中其他顶点的最短路径,并限定各边上的权值大于或等于0 。
狄克斯特拉(Dijkstra)算法基本思想:
1. 设G=(V,E)是一个带权有向图,把图中顶点集合V分成两组。
(1)第一组为已求出最短路径的顶点集合,用S表示,初值为{v},渐增至V,表示顶点都加入完毕。
(2)第二组为其余未确定最短路径的顶点集合,用U表示 。
2. 按最短路径长度的递增次序依次把第二组的顶点加入S中,在加入的过程中,总保持从源点v到S中各顶点的最短路径,长度不大于从源点v到U中任何顶点的最短路径长度。
图3-最短路径
假设S中的v是起始点,而u是已经加入到S中的顶点,而v到u之间这样的路径长度为Cvu,1如果在选择最短路径的过程中又把顶点k加入进来。则v到u又产生了新的路径,而新的路径长度为Cvk + Cku 。
从上图可知,现在v到u有两条路径,有可能新的路径长度比原来的路径长度要短,因此要从这两条路径中选择出一条最短的路径长度,即min(Cvk + Cku, Cvu)。
Dijkstra算法的实现需要用到三个数组:
s数组:表示某一个顶点是否加入到数组S中。
dist数组:从源点到各个点的最小距离。
path数组:记录路径,初值为源点到达各个顶点的路径。
4. Dijkstra算法实现步骤
图4
在开始时,只有顶点0加入到S中,那么在S数组中S[0]的值修改为1。而dist数组中是顶点0到达各个顶点的距离(权值),比如顶点0到达顶点1的距离是4,那么在dist数组中把dist[1]的值修改为4,同时在path数组中把path[1]的值赋值为0,即路径0到1。再比如顶点0不能直接到达顶点4,那么dist[4]的值修改、为无穷大,再把path数组中path[4]的值赋值为-1,暂时没有路径到达顶点4。
图5
在加入新的顶点时每次都会从dist数组中加入权值最小的顶点, 于是把顶点1加入到S中后,那么在S数组中S[1]的值修改为1。此时从顶点1开始有以下几条路径:
路径1:0 -> 2 ,权值为6
路径2:0 -> 1 -> 2 , 权值为4 + 1 = 5
路径3:0 -> 1 -> 4 ,权值为4 + 7 = 11
我们要根据以上这些路径,选出最短路径出来,即修改顶点1到达的点:
dist[2]=min { dist[2] , dist[1]+1 } =5 , path[2]=1,相当于挑选出最短路径,即dist[2]=min { 路径1 , 路径2 } = 路径2,也就是说最短路径为路径2。因为比之前的权值要小,所以把dist[2]的值修改为5(表示路径2的权值为5),同时把path[2]修改为1,表示由顶点1到达顶点2(路径1)。
dist[4]=min{dist[4] , dist[1]+7}=11 , path[4]=1,因为顶点1能到达顶点4,所以我们要计算出这条路径的路径长度,并修改顶点4的最短路径,因为到达顶点4的权值比之前到达顶点4的权值(无穷大)要小,所以把顶点4的权值修改为更小的值。
图6
从dist数组中选择权值最小的顶点加入到,于是把顶点2加入到S中后,那么在S数组中S[2]的值修改为1。此时从顶点2开始,有以下几条路径:
路径1:0 -> 1 -> 2 -> 4
路径2:0 -> 1 -> 4
路径3:0 -> 1 -> 2 -> 5
我们要根据以上这些路径,选出最短路径出来,即修改顶点2到达的点:
dist[4]=min{dist[4] , dist[2]+6}=11 , path[2]不变,相当于dist[4]=min { 路径2 , 路径1} = 路径2,path[2]不变,因为顶点0到达顶点4的最短路径还是路径2,所以顶点2到达的点不需要修改。
dist[5]=min{dist[5],dist[2]+4}=9, path[5]=2,通过顶点2到达顶点5的权值是9,比之前顶点0到达顶点5的权值要小,所以要修改到达顶点5的权值,即把dist[5]的值修改为9,同时把path[5]的值修改为2,表示由顶点2到达顶点5(路径3)。
图7
同理,再把顶点3加入到S中后,那么在S数组中S[3]的值修改为1。此时从顶点3开始,有以下几条路径:
路径1:0 -> 3 -> 2 ,权值为6 + 2 = 8
路径2 : 0 -> 3 -> 5 ,权值为6 + 5 = 11
dist[2]=min{dist[2] , dist[3]+2}=5, path[2]不变, 其实对于路径1来说,还有一条更好的路径到达顶点2,即 0 -> 1 -> 2路径,而这条路径的权值为5,因为这条路径的权值比路径1更小,是一条最短路径,所以path[2]不变。
dist[5]=min{dist[5] , dist[3]+5}=9 , path[5]不变,对于路径2来说,还有一条更好的路径到达顶点5,即 0 -> 1 -> 2
-> 5路径,这条路径的权值为11,因为这条路径的权值比路径2更小,是一条最短路径,所以path[5]不变。
图8
同理,从dist数组中搜索权值最小的顶点,把顶点5加入到S中,把数组S中S[5]的值修改为1,从顶点5开始,有以下几条路径:
路径1:0 -> 1 -> 2 -> 5 -> 4 ,权值为 4 + 1 + 4 + 1 = 10
路径2:0 -> 1 -> 2 -> 5 -> 6 , 权值为 4 + 1 + 4 + 8 = 17
dist[4]=min{dist[4],dist[5]+1}=10, path[4]=5,通过计算路径1和之前记录的到达顶点4的路径(0 -> 1 -> 2)相比, dist数组中记录的路径权值为11,显然路径1是最短路径。于是我们对到达顶点4的权值进行修改,即把dist数组中dist[4]的值修改为10,同时把path数组中path[4]的值修改为5,表示由顶点5到达顶点4(路径1)。
dist[6]=min{dist[6],dist[5]+8}=17, path[5]=5,在计算机路径2和之前记录的到达顶点6的路径相比,显然路径2的权值要比之前的路径的权值(无穷大)要小,所以路径2是最短距离,于是对到达顶点6的权值进行修改,即把dist数组中dist[6]的值修改为17,同时把path数组中path[5]的值修改为5,表示由顶点5到达顶点6(路径2)。
图9
同理,从dist数组中搜索权值最小的顶点,把顶点4加入到S中,把数组S中S[4]的值修改为1,从顶点4开始,有以下几条路径:
路径1:0 -> 1 -> 2 -> 5 -> 4 -> 6 ,权值为 4 + 1 + 4 + 1 + 6 = 16
dist[6]=min{dist[6],dist[4]+6}=16, path[6]=4,通过计算后,很明显,路径1是最短路径,于是修改dist数组和path数组,并把最后一个顶点6加入进来。
5. Dijkstra算法实现
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXV 7
#define INF 99
//图的定义:邻接矩阵
typedef struct MGRAPH{
int n; //顶点数
int e; //边数
int edges[MAXV][MAXV]; //邻接矩阵
} MGraph;
void Ppath(int path[],int i,int v)
{
int k;
k=path[i];
if (k==v)
return;
Ppath(path,k,v);
printf("%d,",k);
}
void Dispath(int dist[],int path[],int s[],int n,int v)
{
int i;
for (i=0; i<n; i++)
if (s[i]==1)
{
printf(" 从%d到%d的最短路径长度为:%d\t路径为:",v,i,dist[i]);
printf("%d,",v);
Ppath(path,i,v);
printf("%d\n\n",i);
}
else
printf("从%d到%d不存在路径\n",v,i);
}
//Dijkstra算法实现
void Dijkstra(MGraph g,int v)
{
//定义辅助存储及赋初值
int mindis,i,j,u;
int s[MAXV];
int dist[MAXV],path[MAXV];
for (i=0; i<g.n; i++)
{
//初始化s数组
s[i]=0;
//初始dist数组
dist[i]=g.edges[v][i];
//初始path数组
if (g.edges[v][i]<INF)
path[i]=v;
else
path[i]=-1;
}
//加入起始顶点
s[v]=1;
//记录路径
path[v]=0;
for (i=0; i<g.n; i++)
{
//选取不在s中且具有最小距离的顶点u
mindis=INF;
for (j=0; j<g.n; j++)
if (s[j]==0 && dist[j]<mindis)
{
//记录最小距离的顶点
u=j;
mindis=dist[j];
}
//顶点u加入s中
s[u]=1;
//修改不在s中的顶点的距离
for (j=0; j<g.n; j++)
if (s[j]==0)
//这个顶点的距离要小于INF(与顶点u相连的顶点),且新的路径长度小于原先的路径长度
if (g.edges[u][j]<INF && dist[u]+g.edges[u][j]<dist[j])
{
//修改为最短路径
dist[j]=dist[u]+g.edges[u][j];
//并记录最短路径的顶点
path[j]=u;
}
}
//输出最短路径
Dispath(dist , path , s , g.n , v);
}
int main(void)
{
int A[MAXV][MAXV] = {
{0,4,6,6,INF,INF,INF},
{INF,0,1,INF,7,INF,INF},
{INF,INF,0,INF,6,4,INF},
{INF,INF,2,0,INF,5,INF},
{INF,INF,INF,INF,0,INF,6},
{INF,INF,INF,INF,1,0,8},
{INF,INF,INF,INF,INF,INF,0},
};
int i;
int j;
int v;
MGraph g;
g.n = 7;
g.e = 12;
for(i = 0; i <g.n; i++)
{
for(j = 0; j < g.n; j++)
{
g.edges[i][j] = A[i][j];
}
}
//起始点
v = 0;
//Dijkstra算法
printf("从一个顶点到其余各顶点的最短路径:\n\n");
Dijkstra(g,v);
printf("\n");
return 0;
}
测试结果: