目录
一、介绍
概念
路径:
从图或者网中的某个顶点,到另一个顶点所经过的点和边的集合,可以用多种储存方式来储存
最短路径:
从图中一个起点到其余各个顶点的最短路径
路径长度:
一条路径上所经过的边的数目
带权路径长度:
路径上所经过的边的权值之和
最短路径长度(最短距离):
最短路径的(带权值)路径长度
tips:因为不带权的情况比较简单,我们就带权值的连通网来进行举例
如该图所示:我们设起点为顶点0,那么到达各个顶点就有多条路径,到达顶点1的有:0-1,到达顶点2的有:0-1-2、0-3-2,到达顶点3的有:0-3,到达顶点4的有:0-4、0-1-2-4,那么该图的最短路径就是0-1、0-3-2、0-4(怎么求出来的后面会解释),0-1这条路径的路径长度为1,带权路径长度为5
最短路径与最小生成树的不同点
1.最短路径的操作对象是有向图或者有向网,而最小生成树的操作对象是无向图
2.最短路径有一个起点,但是最小生成树没有
3.最短路径所关注的点是起点到其余各点的路径最短,然而最小生成树所关注的点是整棵树的代价最小
二、Dijkstra算法
算法介绍
狄克斯特拉算法是一种使用了回溯思想的算法,并使用辅助数组用来搜寻最短路径,每次迭代都可以得到一个最短路径,因此是一个非常稳定的算法
算法思想与算法举例
设我们有下图这么一个连通网
1.首先设置一个二维辅助数组distance[i][j](其中i为当前顶点,j表示从顶点i到顶点j的权值,若顶点i与顶点j不相连,则距离为无穷大,且顶点i到顶点i的距离为0)
2.选择一个起点,由该顶点进行dijkstra算法,我们选择顶点0
3.我们观察顶点0的出弧(也就是顶点0能到达的顶点),发现0能直接到达的顶点有顶点3和顶点2,那么我们就可以得到distance[0][]如下图所示
由此我们可以看到除去与本身的距离,<0,2>这条弧权值是最小的,那么我们就可以直接确定顶点0到顶点2的最短路径就是0-2
4.然后我们从顶点2出发,一样得到顶点2的distance[2][j]如下图所示
除去与本身的距离我们可以发现<2,5>这条弧上的权值是最小的,注意:此时我们并不是直接就把0-2-5这条路径作为顶点0到达顶点5的最短路径,而是返回到上一个顶点进行查询,也就是查询是否存在<0,5>这条弧且其权值小于从起点到顶点2的最短路径长度加上<2,5>这条弧的权值的路径(简单的来说就是回退到顶点0看一下能否有更短的可以直接从顶点0到达顶点5的路径),经过查询之后发现不存在,因此直接将0-2-5确定为顶点0到顶点5的最短路径。
5.然后我们从顶点5出发,进行同样的操作
得到权值最小的弧为<5,3>,此时我们回退到顶点2,查询是否存在<2,3>这条弧并判断从起点到达顶点2的最短路径长度加上<2,3>这条弧的权值是否小于0-2-5-3这条带权路径的长度,经查询之后发现不存在这条弧那么直接确定0-2-5-3这条路径就是从起点到顶点3的最短路径
当最后一个顶点无法找到任何一条出弧时,这个时候我们就要进行回溯,找到之前所记录的权值最小的边,按照这个规则我们找到了<2,1>这条弧,我们直接对顶点1进行访问,对顶点1进行访问完毕之后我们按照之前的规则,查询是否存在<2,4>这条弧并且<2,4>这条弧的权值小于2-1-4这条带权路路径的长度,经查询之后发现不存在那么我们就可以直接确定0-2-1-4这条路径就是从起点到顶点4的最短路径
当全部顶点被访问完毕,此算法结束
算法实现
要实现这个算法,我们需要创建几个数组用来辅助
distance[i]:用来表示起点到顶点i的最短路径的距离
prev[i]:用来表示起点到顶点i的最短路径的前驱顶点
flag[i]:用来表示顶点i是否被访问过
1.无向图类
class undigraph
{
public:
//int vertexnum=0;
int vertexnum=7;
//图以邻接矩阵的形式存储
//int adjmatrix[maxsize][maxsize];//如果要自己输入图就把注释掉的取消注释并将对应代码注释掉
int adjmatrix[7][7]=
{
{0,5,7,infinity,infinity,infinity,2},
{5,0,infinity,9,infinity,infinity,3},
{7,infinity,0,infinity,8,infinity,3},
{infinity,9,infinity,0,infinity,4,infinity},
{infinity,infinity,8,infinity,0,5,4},
{infinity,infinity,infinity,4,5,0,6},
{2,3,3,infinity,4,6,0}
};
int start=0;
//int start;
int distance[maxsize];//起点与各顶点间最短距离数组
int prev[maxsize];//起点与各顶点之间的最短路径的前驱数组
int flag[maxsize];//顶点访问数组用来表示是否已经被访问
//创建图
void createundigraph();
//初始化
void initdistance();
void initpre();
void initflag();
//打印数组
void printdistance();//打印距离数组
void printprev();//打印前驱数组
void printpath();//打印最短路径
//最短路径算法
void dijkstra();
};
2.初始化
void undigraph::initdistance()
{
for (int i = 0; i < vertexnum; i++)
{
distance[i]=adjmatrix[start][i];//初始起点到达其他所有顶点的最短路径长度就是邻接矩阵中的权值
}
}
void undigraph::initpre()
{
for (int i = 0; i < vertexnum; i++)
{
prev[i]=i;//初始所有的点所在的最短路径的前驱顶点都是自身
}
}
void undigraph::initflag()
{
for (int i = 0; i < vertexnum; i++)
{
flag[i]=0;//初始所有顶点都未被访问过
}
}
3.dijkstra算法
void undigraph::dijkstra()
{
//用户输入起点
cout<<"请输入起点:"<<endl;
cin>>start;
//初始化顶点
//1.对顶点进行访问
flag[start]=1;
//2.起点到起点的最短路径长度为0
distance[start]=0;
//定义一个变量用来储存即将访问的顶点
int k=start;
//因为起点已经被初始化了,此时我们还剩下vertexnum-1个顶点需要进行访问和求取最短路径
for (int i = 0; i < vertexnum-1; i++)
{
//每次寻找当前顶点的最短路径的时候都要将最小权值初始化为无穷
int min=infinity;
//经过循环之后找出与当前点相连接的权值最小的顶点并记录其下标
for (int j = 0; j < vertexnum; j++)
{
if (flag[j]==0&&distance[j]<min)
{
min=distance[j];
k=j;
}
}
//对其进行访问
flag[k]=1;
//对这个访问的顶点进行最短路径修正
int temp;
for (int p = 0; p < vertexnum; p++)
{
if (adjmatrix[k][p]==infinity)//如果最后访问的顶点k与当前所选中的顶点不连通的话
{
temp=infinity;//将当前最短距离设置为无穷
}
else
{
temp=min+adjmatrix[k][p];//若连通就将当前距离设置为起点到顶点k的最短距离加上(k,p)的权值
}
if (flag[p]==0&&temp<distance[p])//如果顶点p未被访问过,且temp小于顶点直接到达顶点p的距离
{
distance[p]=temp;//就将temp设置为起点到达顶点p的最短距离
prev[p]=k;//并将顶点p的前驱顶点设置为顶点k
}
}
}
}
4.打印最短距离数组、前驱顶点数组、最短路径
void undigraph::printdistance()
{
cout<<"=========distance========="<<endl;
for (int i = 0; i < vertexnum; i++)
{
cout<<distance[i]<<" ";
}
cout<<endl;
}
void undigraph::printprev()
{
cout<<"=========prev========="<<endl;
for (int i = 0; i < vertexnum; i++)
{
cout<<prev[i]<<" ";
}
cout<<endl;
}
void undigraph::printpath()
{
for (int i = 0; i < vertexnum; i++)
{
cout<<"顶点"<<start<<"到顶点"<<i<<"的最短路径为;"<<endl;
cout<<i<<"<---";
int tmp=prev[i];
while (true)
{
if (tmp==i)
{
break;
}
if (tmp==prev[tmp])
{
cout<<tmp<<"<---";
break;
}
cout<<tmp<<"<---";
tmp=prev[tmp];
}
cout<<start<<endl;
}
}
5.全部代码
#include<stdio.h>
#include<iostream>
#define maxsize 100
#define infinity 999
using namespace std;
class undigraph
{
public:
//int vertexnum=0;
int vertexnum=7;
//图以邻接矩阵的形式存储
//int adjmatrix[maxsize][maxsize];//如果要自己输入图就把注释掉的取消注释并将对应代码注释掉
int adjmatrix[7][7]=
{
{0,5,7,infinity,infinity,infinity,2},
{5,0,infinity,9,infinity,infinity,3},
{7,infinity,0,infinity,8,infinity,3},
{infinity,9,infinity,0,infinity,4,infinity},
{infinity,infinity,8,infinity,0,5,4},
{infinity,infinity,infinity,4,5,0,6},
{2,3,3,infinity,4,6,0}
};
int start=0;
//int start;
int distance[maxsize];//起点与各顶点间最短距离数组
int prev[maxsize];//起点与各顶点之间的最短路径的前驱数组
int flag[maxsize];//顶点访问数组用来表示是否已经被访问
//创建图
void createundigraph();
//初始化
void initdistance();
void initpre();
void initflag();
//打印数组
void printdistance();//打印距离数组
void printprev();//打印前驱数组
void printpath();//打印最短路径
//最短路径算法
void dijkstra();
};
void undigraph::createundigraph()
{
int tempdata;
int adjvertex;
int i=0;
for(;;i++)
{
cout<<"请输入顶点"<<i<<"数据:"<<endl;
cin>>tempdata;
vertexnum+=1;
if (tempdata==-1)
{
break;
}
cout<<"请输入该顶点的相邻顶点:"<<endl;
cin>>adjvertex;
adjmatrix[i][adjvertex]=1;
adjmatrix[adjvertex][i]=1;
}
cout<<"邻接矩阵构建完毕"<<endl;
}
void undigraph::initdistance()
{
for (int i = 0; i < vertexnum; i++)
{
distance[i]=adjmatrix[start][i];//初始起点到达其他所有顶点的最短路径长度就是邻接矩阵中的权值
}
}
void undigraph::initpre()
{
for (int i = 0; i < vertexnum; i++)
{
prev[i]=i;//初始所有的点所在的最短路径的前驱顶点都是自身
}
}
void undigraph::initflag()
{
for (int i = 0; i < vertexnum; i++)
{
flag[i]=0;//初始所有顶点都未被访问过
}
}
void undigraph::dijkstra()
{
//用户输入起点
cout<<"请输入起点:"<<endl;
cin>>start;
//初始化顶点
//1.对顶点进行访问
flag[start]=1;
//2.起点到起点的最短路径长度为0
distance[start]=0;
//定义一个变量用来储存即将访问的顶点
int k=start;
//因为起点已经被初始化了,此时我们还剩下vertexnum-1个顶点需要进行访问和求取最短路径
for (int i = 0; i < vertexnum-1; i++)
{
//每次寻找当前顶点的最短路径的时候都要将最小权值初始化为无穷
int min=infinity;
//经过循环之后找出与当前点相连接的权值最小的顶点并记录其下标
for (int j = 0; j < vertexnum; j++)
{
if (flag[j]==0&&distance[j]<min)
{
min=distance[j];
k=j;
}
}
//对其进行访问
flag[k]=1;
//对这个访问的顶点进行最短路径修正
int temp;
for (int p = 0; p < vertexnum; p++)
{
if (adjmatrix[k][p]==infinity)//如果最后访问的顶点k与当前所选中的顶点不连通的话
{
temp=infinity;//将当前最短距离设置为无穷
}
else
{
temp=min+adjmatrix[k][p];//若连通就将当前距离设置为起点到顶点k的最短距离加上(k,p)的权值
}
if (flag[p]==0&&temp<distance[p])//如果顶点p未被访问过,且temp小于顶点直接到达顶点p的距离
{
distance[p]=temp;//就将temp设置为起点到达顶点p的最短距离
prev[p]=k;//并将顶点p的前驱顶点设置为顶点k
}
}
}
}
void undigraph::printdistance()
{
cout<<"=========distance========="<<endl;
for (int i = 0; i < vertexnum; i++)
{
cout<<distance[i]<<" ";
}
cout<<endl;
}
void undigraph::printprev()
{
cout<<"=========prev========="<<endl;
for (int i = 0; i < vertexnum; i++)
{
cout<<prev[i]<<" ";
}
cout<<endl;
}
void undigraph::printpath()
{
for (int i = 0; i < vertexnum; i++)
{
cout<<"顶点"<<start<<"到顶点"<<i<<"的最短路径为;"<<endl;
cout<<i<<"<---";
int tmp=prev[i];
while (true)
{
if (tmp==i)
{
break;
}
if (tmp==prev[tmp])
{
cout<<tmp<<"<---";
break;
}
cout<<tmp<<"<---";
tmp=prev[tmp];
}
cout<<start<<endl;
}
}
int main()
{
undigraph ug;
ug.initdistance();
ug.initflag();
ug.initpre();
ug.dijkstra();
ug.printdistance();
ug.printprev();
ug.printpath();
return 0;
}
代码执行结果
我们将下图导入到代码中得到执行结果
三、Floyd算法
算法介绍
Floyd算法的算法核心是贪心以及动态规划
算法思想
1.插点法
设顶点 vi(开始顶点) 到顶点 vj(结束顶点) 的当前最短路径距离为,此时我们找到一个顶点 vk(中间顶点) ,且 vk 同时与 vi 和 vj 相通,此时我们计算 vi 到 vk 的距离 和 vk 到 vj 的距离,若(+)<就说明顶点vk的插入可以使得顶点 vi 到顶点 vj 的路径距离变短,那么我们就更新顶点 vi 到顶点 vj 的当前最短路径距离为 (+)。
2.辅助数组
该算法还需要设置两个数组用来辅助,分别是distance[][]和path[][]
distance[i][j]:储存顶点i到顶点j的最短距离(用来存储最短路径的距离)
path[i][j]:用来储存顶点i到顶点j的最短路径前驱节点(用来存储最短路径)
tips:在这里我觉得有必要解释一下path[][]中前驱节点的意思,如下图所示
如果求顶点0到顶点3这条路径的前驱节点,因为顶点2在终点3之前,因此顶点2就是前驱节点,但是在实际代码编程中,有时候会发生前驱顶点到了前面的情况,但是并不会影响最短路径距离的计算
初始化distance[i][j]:将其初始化为该图以邻接矩阵形式存储的形式,其中不直接相连的位置最短距离初始化为无穷大∞(在编程中可以define infinity为一个很大的数用来表示无穷大),直接相连的位置其最短距离就初始化为该边上的权值,自己与自己的最短距离初始化为0
初始化path[][]:当一个顶点到达另一个顶点没有确定的最短路径时,我们就设置顶点i到顶点j的前驱节点,也就是path[i][j]为 i,也就是初始化顶点0到其他任何顶点(包括自己)的前驱节点为自身本身
3.三层循环
Floyd算法的全局是,将每个顶点都作为中间顶点进行插入,并不断更改开始顶点和结束顶点,若有符合结果的就更新distance[][]和path[][]的值,直到所有顶点都作为中间顶点并全部遍历过一次后算法结束。因此三层循环由外到内分别是 中间顶点、开始顶点、结束顶点。
算法举例
如上图所示是一个无向连通网,我们先对两个数组进行初始化得到下图:
然后我们从顶点0开始,此时0顶点作为中间顶点,我们对其他的所有顶点进行遍历,找到连通的顶点,我们先找到顶点1,此时顶点1就成为了开始顶点,然后我们继续寻找找到顶点2作为结束顶点,此时我们可以知道distance[1][2]=infinity,distance[1][0]+distance[0][2]=12,则有distance[1][0]+distance[0][2]<distance[1][2],那我们就更新distance[1][2]为12,并且顶点0作为这个新的当前最短路径中的一个中间顶点,我们就更新path[1][2]=0
更新完毕之后,此时结束顶点作为最内层循环,继续查找下一个连通的顶点,找到了顶点6(注意,在程序中是会运行到以自己为开始顶点和结束顶点的情况的,但是这种情况并不会影响结果,因此我们只需要关注不同的顶点的情况即可),此时顶点6就作为结束顶点,distance[1][6]=3,distance[1][0]+distance[0][6]=7,distance[1]<distance[1][0]+distance[0][6],无需更新distance[1][6]和path[1][6]
此时我们已经可以发现已经没有其他的结束顶点是连通的了,那我们就进入第二层循环也就是开始顶点的循环,我们找到了顶点2作为开始顶点,然后再次进入结束顶点的循环,我们找到了顶点1,(此时我们可以发现这一步跟上面所展示的第一步很像,是因为对于无向图来说是双向的),我们依葫芦画瓢可以得到新的distance[2][1]=12,新的path[2][1]=0
就这样以此类推,直到最外层循环也就是中间顶点循环执行完毕。
算法实现
1.将图使用邻接矩阵进行储存,我这里为了方便测试,直接将图输入在了类中,当然为了适应其他的图,我也写了一个创建图的函数createundigraph()
无向图类:
class undigraph
{
public:
//int maxvertex;
int maxvertex=7;
//图以邻接矩阵的形式存储
//int adjmatrix[maxsize][maxsize];
int adjmatrix[7][7]=
{
{0,5,7,infinity,infinity,infinity,2},
{5,0,infinity,9,infinity,infinity,3},
{7,infinity,0,infinity,8,infinity,3},
{infinity,9,infinity,0,infinity,4,infinity},
{infinity,infinity,8,infinity,0,5,4},
{infinity,infinity,infinity,4,5,0,6},
{2,3,3,infinity,4,6,0}
};
int distance[maxsize][maxsize];//两点间最短距离数组
int path[maxsize][maxsize];//两点间最短距离路径数组
//创建图
void createundigraph();
//初始化
void initdistance();
void initpath();
//打印数组
void printdistance();//打印距离数组
void printpathmatrix();//打印最短路径矩阵
void printpath();//打印最短路径
//最短路径算法
void floyd();
};
2.对distance[][]和path[][]进行初始化
distance[][]初始化函数:
void undigraph::initdistance()
{
for (int i = 0; i < maxvertex; i++)
{
for (int j = 0; j < maxvertex; j++)
{
distance[i][j]=adjmatrix[i][j];//使其与图的邻接矩阵相同
}
}
}
path[][]初始化函数:
void undigraph::initpath()
{
for (int i = 0; i < maxvertex; i++)
{
for (int j = 0; j < maxvertex; j++)
{
path[i][j]=i;//将前驱顶点初始化为自身
}
}
}
3.调用Floyd算法
void undigraph::floyd()
{
for (int i = 0; i <maxvertex ; i++)
{
for (int j = 0; j <maxvertex ; j++)
{
for (int k = 0; k <maxvertex ; k++)
{
int startdistance=distance[j][i];
int enddistance=distance[i][k];
if ((startdistance+enddistance)<distance[j][k])
{
distance[j][k]=startdistance+enddistance;
path[j][k]=i;
}
}
}
}
}
4.全部代码
#include<stdio.h>
#include<iostream>
#define infinity 999
#define maxsize 100
using namespace std;
class undigraph
{
public:
//int maxvertex;
int maxvertex=7;
//图以邻接矩阵的形式存储
//int adjmatrix[maxsize][maxsize];
int adjmatrix[7][7]=
{
{0,5,7,infinity,infinity,infinity,2},
{5,0,infinity,9,infinity,infinity,3},
{7,infinity,0,infinity,8,infinity,3},
{infinity,9,infinity,0,infinity,4,infinity},
{infinity,infinity,8,infinity,0,5,4},
{infinity,infinity,infinity,4,5,0,6},
{2,3,3,infinity,4,6,0}
};
int distance[maxsize][maxsize];//两点间最短距离数组
int path[maxsize][maxsize];//两点间最短距离路径数组
//创建图
void createundigraph();
//初始化
void initdistance();
void initpath();
//打印数组
void printdistance();//打印距离数组
void printpathmatrix();//打印最短路径矩阵
void printpath();//打印最短路径
//最短路径算法
void floyd();
};
void undigraph::createundigraph()
{
int tempdata;
int adjvertex;
int i=0;
for(;;i++)
{
cout<<"请输入顶点"<<i<<"数据:"<<endl;
cin>>tempdata;
if (tempdata==-1)
{
break;
}
cout<<"请输入该顶点的相邻顶点:"<<endl;
cin>>adjvertex;
if (adjvertex>=maxvertex)
{
maxvertex=adjvertex+1;
}
adjmatrix[i][adjvertex]=1;
adjmatrix[adjvertex][i]=1;
}
cout<<"邻接矩阵构建完毕"<<endl;
}
void undigraph::initdistance()
{
for (int i = 0; i < maxvertex; i++)
{
for (int j = 0; j < maxvertex; j++)
{
distance[i][j]=adjmatrix[i][j];
}
}
}
void undigraph::initpath()
{
for (int i = 0; i < maxvertex; i++)
{
for (int j = 0; j < maxvertex; j++)
{
path[i][j]=i;
}
}
}
void undigraph::printdistance()
{
cout<<"==========distance=========="<<endl;
for (int i = 0; i < maxvertex; i++)
{
for (int j = 0; j < maxvertex; j++)
{
cout<<distance[i][j]<<" ";
}
cout<<endl;
}
}
void undigraph::printpathmatrix()
{
cout<<"==========path=========="<<endl;
for (int i = 0; i < maxvertex; i++)
{
for (int j = 0; j < maxvertex; j++)
{
cout<<path[i][j]<<" ";
}
cout<<endl;
}
}
void undigraph::printpath()
{
cout<<"请输入起点顶点:"<<endl;
int startvertex;
cin>>startvertex;
cout<<"请输入结束顶点:"<<endl;
int endvertex;
cin>>endvertex;
int tempvertex=startvertex;
int frontvertex=path[startvertex][endvertex];;
cout<<startvertex<<"---->";
while (true)
{
if (tempvertex!=frontvertex)
{
cout<<frontvertex<<"---->";
tempvertex=frontvertex;
frontvertex=path[frontvertex][endvertex];
}
else
{
cout<<endvertex<<endl;
break;
}
}
}
void undigraph::floyd()
{
for (int i = 0; i <maxvertex ; i++)
{
for (int j = 0; j <maxvertex ; j++)
{
for (int k = 0; k <maxvertex ; k++)
{
int startdistance=distance[j][i];
int enddistance=distance[i][k];
if ((startdistance+enddistance)<distance[j][k])
{
distance[j][k]=startdistance+enddistance;
path[j][k]=i;
}
}
}
}
}
int main()
{
undigraph ug;
ug.initdistance();
ug.initpath();
ug.printdistance();
ug.printpathmatrix();
ug.floyd();
ug.printdistance();
ug.printpathmatrix();
ug.printpath();
return 0;
}
代码执行结果
三、总结
个人认为相对于dijkstra算法来说,floyd算法的代码更具有可读性和便于理解,但是dijkstra算法因其每轮迭代都可以得到一个点的最短路径的稳定性,在实际解决问题时我个人更加推荐,因此需要对该算法的代码进行加深一步理解,如果觉得本文写的不错的请点点赞,有写得不好的请多多指正。