7.6 图的最短路径
在一个赋权有向图G={V,E}中,每一条边都有一个权值W(Weight)(该权值通常称之为成本或距离),求G图中某一个顶点v1到其他顶点具有最小权值总和的路径,这类问题就称为最短路径问题(TheShortest Path Problem)。
本节将探讨单点对全部顶点的最短路径及所有顶点之间的最短路径。
7.6.1 单点对全部顶点的最短路径
问题的提法是:给定一个有向带权图G={V,E}与求一个固定顶点v1(即源点),要求找出从v1到G中其他各顶点的最短路径。这类问题称为单源最短路径问题。
为了求得这些最短路径,狭克斯特拉(Dijkstra)提出了按权值的递增次序,逐步产生最短路径的算法。首先求出权值最小的一条最短路径,然后参照它求出权值次小的一条最短路径,依次类推,直到从顶点v到其他各顶点的最短路径全部求出为止。
【范例6】考虑下图所示的有向带权图,边上的数字为该边的权值(成本或距离),设源点v1=6,给出它到各个顶点的最短路径。
初始状态时,被选顶点集合S={6},最短距离集合D={0}。
第1步、D[1]=∞,D[2]=12,D[3]=∞,D[4]=20,D[5]=14,D[2]=12最小
被选顶点集合S={6,2}。
最短距离集合D={0,12}。
2、D[1]=∞,D[2]=12,D[3]=12+6=18,D[4]=20,D[5]=14,D[5]=14最小
被选顶点集合S={6,2,5}。
最短距离集合D={0,12,14}。
3、D[1]=26,D[2]=12,D[3] =12+6=18,D[4]=20,D[5]=14,D[3]=18最小
被选顶点集合S={6,2,5,3}。
最短距离集合D={0,12,14,18}。
4、D[1]=26,D[2]=12,D[3]=18,D[4]=20,D[5]=14。D[4]=20最小
被选顶点集合S={6,2,5,3,4}。
最短距离集合D={0,12,14,18,20}。
5、D[1]=26,D[2]=12,D[3]=18,D[4]=20,D[5]=14。D[1]=26最小
被选顶点集合S={6,2,5,3,4,1}。
最短距离集合D={0,12,14,18,20,26}。
因此,由顶点6到其他各顶点的最短路径如下:
l 顶点6→顶点1,距离26
l 顶点6→顶点2,距离12
l 顶点6→顶点3,距离18
l 顶点6→顶点4,距离20
l 顶点6→顶点5,距离14
下面讨论Dijkstra算法的具体实现:
设集合S存放已经求出的最短路径的终点。初始状态时,集合S中只有一个源点,不妨设为v1。以后每求得一条最短路径(v1,…,vk),就将vk加入到集合S中,直到全部顶点都加入到集合S中,算法就可以结束了。
为了找到从源点v1到其他顶点的最短路径长度,引入一个辅助数组dist[]。它的每一个分量dist[i]表示当前找到的从源点v1终点vi的最短路径的长度。它的初始状态是:若从源点v1到顶点vi有边,则dist[i]为该边上的权值;若从源点v1到顶点vi没有边,则dist[i]的值为INFINITE。
设第一条最短路径为(v1,vk),其中k满足:
dist[k]=min{ dist[i] | vi∈V-{v1},V为图的顶点集合}
假设下一条最短路径的终点为vj,则最短路径或为(v1,vj),或为(v1,vk,vj),其长度或者是边(v1,vj)的权值,或者是dist[k]+边( vk,vj )的权值。
一般情况下,下一条最短路径总是在“由已产生的最短路径再扩充一条边”形成的最短路径中得到。假设S是已求得的最短路径的终点的集合,则可以证明:下一条最短路径必然是从v1出发,中间经过S中的顶点再扩充一条边便可到达顶点vi∈V-S的各条路径中的最短者。即若设下一条最短路径(v1,…,vk)的终点为vk,则有
dist[k]=min{ dist[i] | vi∈V-S }
反证法:设在路径(v1,…,vk)上存在另一个顶点vp∈V-S,使得(v1,…,vp,vk)成为另一条终点不在S,而长度比路径(v1,…,vk)还短的路径。
然而,这个假设不成立。因为我们是按照最短路径的长度递增次序,来逐次产生各条最短路径,因此,长度比这条路径短的所有路径均已产生,而且它们的终点也一定已在集合S中,故假设不成立。
在每次求得一条最短路径之后,将其终点vk加入集合S,然后对所有的vi∈V-S,修改其dist[i],使 dist[i]=min{ dist[i],dist[k] + 边(vk,vi )的权值 }。
【范例程序】用狭克斯特拉(dijksta)算法求单点对全部顶点的最短距离。
/*
[名称]:ch07_08.cpp
[示范]:Dijkstra算法(单源点对全部顶点的最短距离)
*/
#include<iostream>
using namespace std;
#define N 7 //顶点数+1
#define INFINITE 99999//无穷大
intEdgeData[9][3]={{1,2,8},{2,3,6},{2,5,16},{3,4,10},{3,6,18},
{5,1,12},{6,2,12},{6,4,20},{6,5,14}};//有向图的边集合
int S[N];//最短路径的顶点集合
int V[N];//图的顶点集合
//邻接矩阵初始化
void InitialMatrix(intAdjacencyMatrix[N][N])
{ for(int i=1;i<N;i++)
for(int j=1;j<N;j++)
{ if(i==j)
AdjacencyMatrix[i][j]=0;//主对角线元素均为0
else
AdjacencyMatrix[i][j]=INFINITE;//其余元素均置为无穷大
}
}
//生成邻接矩阵
void CreateMatrix(intAdjacencyMatrix[N][N],int EdgeNum)
{ //据边集合EdgeData改写邻接矩阵
inti,VertexI,VertexJ;
for(i=0;i<EdgeNum;i++)
{ VertexI=EdgeData[i][0];//起点编号
VertexJ=EdgeData[i][1];//终点编号
AdjacencyMatrix[VertexI][VertexJ]=EdgeData[i][2];//成本
}
}
//输出邻接矩阵
void DisplayMatrix(intAdjacencyMatrix[N][N])
{ int i,j;
//输出表头
cout<<endl<<"有向图的邻接矩阵"<<endl;
cout<<"顶点\t";
for(i=1;i<N;i++)
cout<<"vex"<<i<<"\t";
cout<<endl;
//输出邻接矩阵
for(i=1;i<N;i++)
{ cout<<"vex"<<i<<"\t";
for(j=1;j<N;j++)
{ if(AdjacencyMatrix[i][j]==INFINITE)
cout<<"x"<<"\t";//无穷大用x替代
else
cout<<AdjacencyMatrix[i][j]<<"\t";
}
cout<<endl;
}
cout<<endl;
}
//查找最小路径长度
void FindMinEdge(int dist[N],int &x,int&y)
{//x终点编号,y最小路径长度
inti,v;
intmin=INFINITE;
for(i=1;i<N;i++)
{//在剩余顶点集找具有最小路径长度的顶点
if(V[i]==1 && dist[i]>0&& dist[i]<min)
{ v=i;
min=dist[i];
}
}
x=v;
y=min;
}
//改变最短路径距离
void ChangeDist(int v,int dist[N],intAdjacencyMatrix[N][N])
{ inti,w;
for(i=1;i<N;i++)
{ if(V[i]==1)//在剩余顶点集改变最小路径长度
{ w=AdjacencyMatrix[v][i];//获取与顶点v相关边的权值
if(w<INFINITE&& dist[i]>0 && dist[v]+w<dist[i])
dist[i]=dist[v]+w;
}
}
}
//狭克斯特拉算法(求单源点对全部顶点的最短路径)
void Dijkstra(int vertex,int dist[N],intAdjacencyMatrix[N][N])
{ int D[N];//纪录最小路径长度
inti,x,y;
S[1]=vertex;//源点进入最小路径顶点集
V[vertex]=0;//标识源点在原顶点集中被剔除
D[1]=0;//源点到源点的距离置0
for(i=1;i<N-1;i++)
{ FindMinEdge(dist,x,y);
S[i+1]=x;//具有最小路径长度的终点进入
V[x]=0;//从原顶点集中剔除
D[i+1]=y;//纪录最小路径长度
ChangeDist(x,dist,AdjacencyMatrix);
}
//输出
cout<<"源点对全部顶点的最短路径长度"<<endl;
for(i=1;i<N;i++)
cout<<"顶点"<<vertex<<"=>"<<S[i]<<": 距离"<<D[i]<<endl;
cout<<endl;
}
//主函数
void main()
{ inti,dist[N];
intAdjacencyMatrix[N][N];//定义邻接矩阵
int vertex=6;//单源点编号
for(i=0;i<N;i++)
{ S[i]=0;//设图的顶点均不在最小路径集合中
V[i]=1;//设图的顶点均在图的顶点集中
}
InitialMatrix(AdjacencyMatrix);//邻接矩阵初始化
CreateMatrix(AdjacencyMatrix,9);//据边集合创建邻接矩阵
DisplayMatrix(AdjacencyMatrix);//输出邻接矩阵
//dist[]初始化
for(i=1;i<N;i++)
dist[i]=AdjacencyMatrix[vertex][i];
Dijkstra(vertex,dist,AdjacencyMatrix);//狭克斯特拉算法
system("pause");
}
(该程序存在着一个缺点,它没有给出最短路径,仅给出最短路径的距离。作为练习,请为该程序配置一个纪录最短路径的函数)
7.6.2顶点之间的最短路径
由于Dijkstra方法只能求出某一点到其他顶点的最短路径,如果要求出图中任意两点甚至所有顶点间的最短路径,则必须使用弗洛伊德(Floyd)算法。
设有向图的顶点数为,Floyd算法如下:
1、,这里,为有向边的成本,若无边则用(无穷大)表示。
2、
其中,表示从顶点经过顶点到达顶点的最短路径。
3、代表到的最短距离,矩阵是所要求的最短路径成本矩阵。
例如,用弗罗伊德(Floyd)算法求下图中各顶点间的最短路径的步骤如下:
1、求,为有向边的成本,若无边则用(无穷大)表示。
2、求,即求由顶点经过顶点1到达顶点的最短路径。
依序求得其他各顶点的值可得到矩阵。
3、求,即求由顶点经过顶点2到达顶点的最短路径。
依序求得其他各顶点的值可得到矩阵。
4、求出,即求由顶点经过顶点3到达顶点的最短路径。
依序求得其他各顶点的值可得到矩阵。
由上例可知,如果一个赋权有向图有个顶点,我们只有生成了该图的邻接矩阵,对它执行次循环与相应操作,便可逐一产生,而就是我们的所求。
【范例程序】用弗洛伊德(Floyd)算法求所有顶点两两之间的最短距离。
/*
[名称]:ch07_09.cpp
[示范]:Floyd算法(所有顶点两两之间的最短距离)
*/
#include<iostream>
using namespace std;
#define N 4 //顶点数+1
#define INFINITE 99999//无穷大
//有向图的边集合
intEdgeData[5][3]={{1,2,4},{2,1,6},{2,3,2},{1,3,11},{3,1,3}};
//邻接矩阵初始化
void InitialMatrix(intAdjacencyMatrix[N][N])
{ for(int i=1;i<N;i++)
for(int j=1;j<N;j++)
{ if(i==j)
AdjacencyMatrix[i][j]=0;//主对角线元素均为0
else
AdjacencyMatrix[i][j]=INFINITE;//其余元素均置为无穷大
}
}
//生成邻接矩阵
void CreateMatrix(intAdjacencyMatrix[N][N],int EdgeNum)
{ //据边集合EdgeData改写邻接矩阵
inti,VertexI,VertexJ;
for(i=0;i<EdgeNum;i++)
{ VertexI=EdgeData[i][0];//起点编号
VertexJ=EdgeData[i][1];//终点编号
AdjacencyMatrix[VertexI][VertexJ]=EdgeData[i][2];//成本
}
}
//输出
void DisplayMatrix(intAdjacencyMatrix[N][N])
{ int i,j;
cout<<"顶点\t";
for(i=1;i<N;i++)
cout<<"vex"<<i<<"\t";
cout<<endl;
for(i=1;i<N;i++)
{ cout<<"vex"<<i<<"\t";
for(j=1;j<N;j++)
{ if(AdjacencyMatrix[i][j]==INFINITE)
cout<<"x"<<"\t";//无穷大用x替代
else
cout<<AdjacencyMatrix[i][j]<<"\t";
}
cout<<endl;
}
cout<<endl;
}
//Floyd算法
void Floyd(int AdjacencyMatrix[N][N])
{ inti,j,k;
for(k=1;k<N;k++)
for(i=1;i<N;i++)
for(j=1;j<N;j++)
if(AdjacencyMatrix[i][k]+AdjacencyMatrix[k][j]<AdjacencyMatrix[i][j])
AdjacencyMatrix[i][j]=AdjacencyMatrix[i][k]+AdjacencyMatrix[k][j];
}
//主函数
void main()
{ intAdjacencyMatrix[N][N];//定义邻接矩阵
InitialMatrix(AdjacencyMatrix);//邻接矩阵初始化
CreateMatrix(AdjacencyMatrix,5);//据边集合创建邻接矩阵
cout<<"有向图的邻接矩阵"<<endl;
DisplayMatrix(AdjacencyMatrix);//输出邻接矩阵
cout<<"所有顶点两两之间的最短距离"<<endl;
Floyd(AdjacencyMatrix);//弗洛伊德算法
DisplayMatrix(AdjacencyMatrix);
system("pause");
}
因Floyd算法较为复杂,也可以用Dijkstra算法,依序以各顶点为起始顶点,如此一来可以得到相同的结果,试对此进行验证。