本人还尚未系统性学习JAVA,故此次笔记学习数据结构C语言版本中的图的最短路径问题。
路径
考虑带权有向图,把一条路劲(仅仅考虑简单路径)上所经边的权值之和定义为该路径的路径长度或称带权路径长度
从源点到终点可能不止一条路径,把路径长度最短的那条路径称为最短路径。很多情况下,两个顶点的最短路径不一定唯一,但最短路径长度一定是唯一的。
最短路径算法
狄克斯特拉(Dijkstra)算法
问题:给定一个带权有向图G与源点v,求从v到G中其他顶点的最短路径,并限定各边上的权值大于或等于0。
单源最短路径问题可以通过 Dijkstra算法 解决
基本思路
设 G=(V,E) 是一个带权有向图,把图中顶点集合V分成两组:
——第一组为已求出最短路径的顶点集合(用S表示,初始时S中只有一个源点,以后每求得一条最短路径v,……,u,就将u加入到集合S中,直到全部顶点都加入到S中,算法结束)
——第二组为其余未求出最短路径的顶点集合(用U表示),按最短路径长度的递增次序把第二组的顶点加入到S中
具体步骤
(1)初始化:S只包含源点即S={v},v 的最短路径为0。U包含除 v 外的其他顶点,U中顶点 i 距离为边上的权值(若v与i有边 < v,i > )或无穷(若i不是v的出边邻接点)。
(2)从U中选取一个距离 v 最小的顶点 u ,把 u 加入到S中(该选定的距离就是 v 到 u 的最短路径长度)。
(3)以 u 为新考虑的中间点,修改U中各顶点 j 的最短路径长度:若从源点 v 到 j( j 属于U)的最短路径长度(经过顶点 u )比原来最短路径长度(不经过顶点 u )短,则修改顶点 j 的最短路径长度。
(4)重复(2)和(3)直到所有顶点都包含在S中。
算法设计
如何存放最短路径长度:
用一维数组dist[ j ]存储。源点 v 默认,dist[ j ]表示源点到顶点 j 的最短路径长度。
如何存放最短路径:
从源点到其他顶点的最短路径有 n-1 条,一条最短路径用一个一维数组表示,所有 n-1 条最短路径可以用二维数组path[ ][ ]存储。
对应的Dijkstra算法如下:
void Dijkstra(MatGraph g,int v) //Dijkstra算法
{ int dist[MAXV],path[MAXV];
int S[MAXV]; //S[i]=1表示顶点i在S中, S[i]=0表示顶点i在U中
int Mindis,i,j,u;
bool flag;
for (i=0;i<g.n;i++)
{ dist[i]=g.edges[v][i]; //距离初始化
S[i]=0; //S[]置空
if (g.edges[v][i]<INF) //路径初始化
path[i]=v; //顶点v到顶点i有边时,置顶点i的前一个顶点为v
else
path[i]=-1; //顶点v到顶点i没边时,置顶点i的前一个顶点为-1
}
disp(dist,path,g.n);
printf("(%d)将顶点%d添加到S集合\n",++count,v);
S[v]=1;path[v]=0; //源点编号v放入S中
for (i=0;i<g.n-1;i++) //循环直到所有顶点的最短路径都求出
{ Mindis=INF; //Mindis置最大长度初值
for (j=0;j<g.n;j++) //选取不在S中(即U中)且具有最小最短路径长度的顶点u
if (S[j]==0 && dist[j]<Mindis)
{ u=j;
Mindis=dist[j];
}
printf(" 求出U中最小的顶点%d\n",u);
printf("(%d)将顶点%d添加到S集合\n",++count,u);
S[u]=1; //顶点u加入S中
flag=false;
for (j=0;j<g.n;j++) //修改不在S中(即U中)的顶点的最短路径
if (S[j]==0)
{
if (g.edges[u][j]<INF)
{
flag=true;
printf(" 考虑顶点%d的邻接点%d:",u,j);
if (dist[u]+g.edges[u][j]<dist[j])
{
dist[j]=dist[u]+g.edges[u][j];
printf("修改其最短路径长度dist[%d]为%d,",j,dist[j]);
path[j]=u;
printf("修改最短路径path[%d]为%d\n",j,u);
}
else
printf("顶点%d的最短路径长度没有修改\n",j);
}
}
if (!flag)
printf(" 顶点%d没有未考虑的邻接点(不修改)\n",u);
disp(dist,path,g.n);
}
Dispath(g,dist,path,S,v); //输出最短路径
}
输出单源最短路径的Dispath()函数如下:
void Dispath(MatGraph g,int dist[],int path[],int S[],int v)
//输出从顶点v出发的所有最短路径
{ int i,j,k;
int apath[MAXV],d; //存放一条最短路径(逆向)及其顶点个数
for (i=0;i<g.n;i++) //循环输出从顶点v到i的路径
if (S[i]==1 && i!=v)
{ printf(" 从顶点%d到顶点%d的路径长度为:%d\t路径为:",v,i,dist[i]);
d=0; apath[d]=i; //添加路径上的终点
k=path[i];
if (k==-1) //没有路径的情况
printf("无路径\n");
else //存在路径时输出该路径
{ while (k!=v)
{ d++; apath[d]=k;
k=path[k];
}
d++; apath[d]=v; //添加路径上的起点
printf("%d",apath[d]); //先输出起点
for (j=d-1;j>=0;j--) //再输出其他顶点
printf(",%d",apath[j]);
printf("\n");
}
}
}
不考虑路径的输出,Dijkstra算法的时间复杂度为O(n^2),其中n图中顶点的个数
弗洛伊德(Floyd)算法
问题:对于一个各边权值大于0的有向图,对每一个顶点 i 不等于 j,求出顶点 i 与顶点 j 之间的最短路径和最短路径长度。
多源最短路径问题可以通过 Floyd 算法 解决
算法思路
假设有向图G=( V , E )采用邻接矩阵存储。设置一个二维数组 A 用于存放当前顶点之间的最短路径长度,分量 A[ i ][ j ]表示当前顶点 i 到 j 的最短路径长度。
递推产生一个矩阵序列:
A[ i ][ j ]:i 到 j 的路径上所经过的顶点编号不大于 k 的最短路径长度
算法设计
用二维数组 A 存储最短路径长度:
A[ i ][ j ]表示考虑顶点0 ~ k 后得出的 i 到 j 的最短路径长度。
数组最后一个A[ i ][ j ] 表示最终的 i 到 j 的最短路径长度。
用二维数组path存放最短路径:
path[ i ][ j ]表示考虑顶点0 ~ k 后得出的 i 到 j 的最短路径。
数组最后一个path[ i ][ j ]表示最终 i 到 j 的最短路径。
对应的Floyd算法如下:
void Floyd(MatGraph g) //Floyd算法
{ int A[MAXV][MAXV],path[MAXV][MAXV];
int i,j,k;
for (i=0;i<g.n;i++)
for (j=0;j<g.n;j++)
{ A[i][j]=g.edges[i][j];
if (i!=j && g.edges[i][j]<INF)
path[i][j]=i; //顶点i到j有边时
else
path[i][j]=-1; //顶点i到j没有边时
}
for (k=0;k<g.n;k++) //依次考察所有顶点
{ for (i=0;i<g.n;i++)
for (j=0;j<g.n;j++)
if (A[i][j]>A[i][k]+A[k][j])
{ A[i][j]=A[i][k]+A[k][j]; //修改最短路径长度
path[i][j]=path[k][j]; //修改最短路径
}
}
Dispath(g,A,path); //输出最短路径
}
输出多源最短路径的Dispath()函数如下:
void Dispath(MatGraph g,int A[][MAXV],int path[][MAXV])
{ int i,j,k,s;
int apath[MAXV],d; //存放一条最短路径中间顶点(反向)及其顶点个数
for (i=0;i<g.n;i++)
for (j=0;j<g.n;j++)
{ if (A[i][j]!=INF && i!=j) //若顶点i和j之间存在路径
{ printf(" 从%d到%d的路径为:",i,j);
k=path[i][j];
d=0; apath[d]=j; //路径上添加终点
while (k!=-1 && k!=i) //路径上添加中间点
{ d++; apath[d]=k;
k=path[i][k];
}
d++; apath[d]=i; //路径上添加起点
printf("%d",apath[d]); //输出起点
for (s=d-1;s>=0;s--) //输出路径上的中间顶点
printf(",%d",apath[s]);
printf("\t路径长度为:%d\n",A[i][j]);
}
}
}
不考虑路径输出,Floyd算法的时间复杂度为O(n^3),其中 n 为图中的顶点的个数。
比较
算法 | 用途 | 时间复杂度 | 特点 |
---|---|---|---|
Dijkstra | 单源最短路径 | O(n^2) | 不适合负权 |
Floyd | 多源最短路径 | O(n^3) | 不适合负权回路 |