今天来看看图的最短路径。
最短路径在许多方面都有广泛的应用,最短路径中的路径一般不再是路径上边的数目,而是路径边上的权值之和。所以最短路径都是用在带权有向图上面的。在最短路径方面,有两个著名的算法,Dijkstra算法和Floyd算法。
1.Dijkstra算法-从某个点到其余各点的最短路径
Dijkstra算法使用了贪心算法思想,主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。但是注意该算法要求图中不存在负权边。因为单纯讲太过空虚,所以我直接用一个例子来讲解。下面是一个带权有向图的示例。
此时他的带权邻接矩阵为(邻接矩阵的定义在传送门):
接下来根据示例图和邻接矩阵来讲解Dijkstra算法是如何求得最短路径的。
1. 一开始我们初始化已选择点集S,我们假设源点为
v0
,所以先把
v0
加入S,此时S = {
v0
}。
2. 我们首先遍历除
v0
之外的所有点,发现
v0
无法直接到达
v1
和
v3
,所以距离为
∞
。而其他三个点中,到
v2
点的距离最近,此时我们选择
v2
加入S,此时S = {
v0
,
v2
}。
3. 接下来再一次遍历除S内元素之外的所有点。我们可以知道,如果想知道
v0
到
vk
的最短距离,则最短距离有两种情况:
- v0 到 vk 上的权值。
- 或者是之间经历了其他顶点,假设经历了一个顶点 vj ,则最短距离 = v0 到 vj 的最短路径 + vj 到 vk 上的权值。而这个 vj 就是我们加入到集合S里面的顶点。
所以我们开始更新第一次的路径长度结果,发现通过新加入的点(即 v2 ),我们可以到达 v3 ,此时路径长度 = v0 到 v2 的权值 + v2 到 v3 权值 = 60。而其他点的路径长度没有被更新或者经历了 v2 后的路径长度并不比更新前的小。最后我们找到路径长度最短的点 v4 ,加入S,此时S = { v0 , v2 , v4 }。
4.再次按照3中的形式进行遍历S之外的点。 此时我们看到加入了
v4
后,我们到达
v3
的路径长度被缩短到了50,这个50 =
v0
到
v4
的权值 +
v4
到
v3
权值,而
v0
到
v4
的权值已经被记录过,所以可以直接使用,所以这个距离很容易就被求出。接下来
v5
的长度更新和
v3
一样,就不在详述了,最后找到路径长度最短的点
v3
,加入S,此时S = {
v0
,
v2
,
v4
,
v3
}。
5. 再次按照3中的形式进行遍历S之外的点。更新了
v5
的路径长,找到路径长度最短的点
v5
,加入S,此时S ={
v0
,
v2
,
v4
,
v3
,
v5
}。而此时我们发现,只剩下
v1
,但是从源点
v0
到
v1
的距离依然是
∞
,说明
v0
没有和
v1
连通。此时我们将
v1
加入S,这时集合S的长度 = 顶点数 = 6,算法结束。
接下来是主要的算法代码,在这里我们使用的图存储结构依然是邻接矩阵。输入的形式是以邻接点对的形式,以0 0 0结束。在代码里,我们看到主程序就是一个嵌套循环,所以它的时间复杂度是O( n2 )。
#include<iostream>
#include<string.h>
#include<queue>
using namespace std;
#define MAX 100
#define INT_MAX 10000
#define max(x,y)(x>y?x:y)
typedef struct{
int matrix[MAX][MAX]; //邻接矩阵
int num;// 最大的顶点数
}Graph;
int initNode=0;
int *shortPath= NULL ;//最短距离
int *S=NULL;
Graph example; //实例图
// 初始化图
void initGraph(){
int i,j;
for(i=0;i<MAX;i++){
for(j=0;j<MAX;j++){
example.matrix[i][j]=INT_MAX;
}
}
example.num=0;
}
// 创建图
void createPoint(){
int i,j,val;
initGraph();
while(cin>>i>>j>>val){
if(i==0 && j==0) break;
example.matrix[i][j]=val;
example.num = max(example.num,i);
//这里偷了个懒,我这里假设顶点之间序号是连续的,没有点之间
//序号跨很大的情况,如果需要的话,大家可以写一个函数改一下
//这里。
example.num = max(example.num,j);
}
}
//输出最后的最短路径
void printResult(){
cout<<"--------------------------"<<endl;
for(int i=0;i<=example.num;i++){
if(i!=initNode)cout<<initNode<<"->"<<i<<":"<<shortPath[i]<< " "<<endl;
}
}
//dijkstra算法
void dijkstra(){
int i,j;
shortPath = new int[example.num+1];
S = new int[example.num+1];
int count = 0; //记录S内元素个数
int nowProP; //记录现在处理的顶点
int minPoint,minval;
for(i=0;i<=example.num;i++){
S[i]=-1;
shortPath[i]=INT_MAX;
}
S[initNode] = 1;
count++;
nowProP = initNode;
//两层循环
while(count<=example.num+1){
minPoint = -1,minval= INT_MAX+1;
for(i=0;i<=example.num;i++){
if(S[i]!=1){
if(example.matrix[initNode][i]< shortPath[i]){
shortPath[i] = example.matrix[initNode][i];
}
if(example.matrix[nowProP][i]+shortPath[nowProP] < shortPath[i]){
shortPath[i] = example.matrix[nowProP][i]+shortPath[nowProP];
}
if(shortPath[i]<minval){minval=shortPath[i];minPoint=i;}
}
}
S[minPoint] = 1;nowProP=minPoint;
count++;
}
}
int main(){
initNode = 0; //这里我们假设源点是v0,也可以改成其他的.
createPoint();
dijkstra();
printResult();
return 0;
}
最后结果是:
0 2 10
0 4 30
0 5 100
1 2 5
2 3 50
3 5 10
4 5 60
4 3 20
0 0 0
--------------------------
0->1:10000
0->2:10
0->3:50
0->4:30
0->5:60
2.Floyd算法-每对点之间的最短路径
Floyd算法主要的是动态规划的思想(下面的介绍来自于这篇博客)。
从任意节点i到任意节点j的最短路径不外乎2种可能,1是直接从i到j,2是从i经过若干个节点k到j。所以,我们假设Dis(i,j)为节点u到节点v的最短路径的距离,对于每一个节点k,我们检查Dis(i,k) + Dis(k,j) < Dis(i,j)是否成立,如果成立,证明从i到k再到j的路径比i直接到j的路径短,我们便设置Dis(i,j) = Dis(i,k) + Dis(k,j),这样一来,当我们遍历完所有节点k,Dis(i,j)中记录的便是i到j的最短路径的距离。
算法描述:
1. 从任意一条单边路径开始。所有两点之间的距离是边的权,如果两点之间没有边相连,则权为无穷大。
2. 对于每一对顶点 u 和 v,看看是否存在一个顶点 w 使得从 u 到 w 再到 v 比己知的路径更短。如果是更新它。
我觉得上面的介绍已经基本上差不多了,下面是一个带权有向图和它的邻接矩阵。
下面是该例子的讲解图。
- D(−1) : 邻接矩阵的初始状态。
- D(0) : 此时我们对于节点0,检查Dis(i,0) + Dis(0,j) < Dis(i,j)是否成立。 循环后我们发现Dis(2,0) + Dis(0,1) = 7 < Dis(2,1) = ∞ ,所以替换掉 Dis(2,1)=7。
- D(1) : 按照2里面的方法,我们发现Dis(0,1) + Dis(1,2) = 6 < Dis(0,2) = 11。替换掉Dis(0,2) 。重复该步骤,直到循环结束。
而根据图我们可以知道这个主程序是三层循环,所以时间复杂度为O( n3 )。但是Dijkstra算法只是从一个源点到其他各点的最短路径,而如果找寻所有点的话,还是需要一个外循环的,所以其实从这个角度看,Dijkstra和Floyd的时间复杂度可以说是一样的,只不过Floyd的思想稍微简单一些。
接下来是主要的算法代码,在这里我们使用的图存储结构依然是邻接矩阵。输入的形式是以邻接点对的形式,以0 0 0结束。输入例子就是这个图。此时我们注意这个邻接矩阵的对角线需要处理成0,而不是 ∞ 。
#include<iostream>
#include<string.h>
#include<queue>
using namespace std;
#define MAX 100
#define INT_MAX 10000
#define max(x,y)(x>y?x:y)
typedef struct{
int matrix[MAX][MAX]; //邻接矩阵
int num;// 最大的顶点数
}Graph;
Graph example; //实例图
// 初始化图
void initGraph(){
int i,j;
for(i=0;i<MAX;i++){
for(j=0;j<MAX;j++){
example.matrix[i][j]=INT_MAX;
}
example.matrix[i][i]=0;
}
example.num=0;
}
// 创建图
void createPoint(){
int i,j,val;
initGraph();
while(cin>>i>>j>>val){
if(i==0 && j==0) break;
example.matrix[i][j]=val;
example.num = max(example.num,i);
//这里偷了个懒,我这里假设顶点之间序号是连续的,没有点之间
//序号跨很大的情况,如果需要的话,大家可以写一个函数改一下
//这里。
example.num = max(example.num,j);
}
}
//输出最短路径矩阵
void printResult(){
cout<<"--------------------------"<<endl;
for(int i=0;i<=example.num;i++){
for(int j=0;j<=example.num;j++){
cout<<example.matrix[i][j]<<" ";
}
cout<<endl;
}
}
//Floyd算法
void Floyd(){
int i,j,k;
for(k=0;k<=example.num;k++){
for(i=0;i<=example.num;i++){
for(j=0;j<=example.num;j++){
if(example.matrix[i][k]+example.matrix[k][j]<example.matrix[i][j]){
example.matrix[i][j] = example.matrix[i][k]+example.matrix[k][j];
}
}
}
}
}
int main(){
createPoint();
Floyd();
printResult();
return 0;
}
最后输出结果:
0 1 4
0 2 11
1 0 6
1 2 2
2 0 3
0 0 0
--------------------------
0 4 6
5 0 2
3 7 0