在图的学习1中我已经总结了图的定义和图的储存这两块知识,今天就要来总结一下图章节的重点:最短路径算法。我将重点讲明弗洛伊德算法和迪杰斯特拉算法的原理,适用范围和代码实现。
Floyd(弗洛伊德算法):我想以Floyd算法作为学习的最短路径算法是最为合适的,Floyd的思想简明实现简单(核心只有五行代码)。我们引入一下情景:
我们要求得任意两个城市之间的最短路程,按Floyd的思想是这样的:如果要从点1走到点3,我们有两种方法,1,直接从点1走到点3路程为6。2,用点2作为中转先从点1到点2再从点2到点3,路程变为了5。路程减少了!那么我们可以猜想通过一个点中转有可能可以降低路程那么两个点甚至更多点呢?这便是Floyd的思想即是计算a到b两点间的最短距离是通过枚举k点来看通过k点中转是否可以降低直接从a到b点距离再更新。代码实现也非常简单:
for(int k=1;k<=n;k++)//枚举k点
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++){
if(map[i][j]>map[i][k]+map[k][j]){
map[i][j]=map[i][k]+map[k][j];//如果中转可以降低距离就更新
}
}
Floyd算法的核心代码只有五行并且可以处理负权边但是它也有一个致命的问题,嵌套的三层循环让他的时间复杂度高达O(n^3),要想时间更快我们就要用到下一种算法。
Dijkstra(迪杰斯特拉算法):
还是上面那个情景不过这次我们得有个固定的起点,比如从点1到各个点的最短距离怎么计算,当然,你可以用弗洛伊德算法,但是在严格的时间限制下弗洛伊德算法可能不那么好用那么就轮到迪杰斯特拉算法登场了,先讲迪杰斯特拉算法的核心思想,我们要借助两个辅助数组check[]和dis[],dis[i]中存储的是当前从起点到i的最短距离,一开始在不知道边与边的关系时都初始化为一个无穷大的数,将起点的dis改为0(起点到起点没有距离),然后我们找出dis值最小的节点以他为起点去更新其他的点,当过起点的节点就用check打上标记,当所有的节点都更新完后dis中存储的就是从起点到各个点的最短距离,给出代码配合理解:
#include<stdio.h>
struct data{
int to,next,w;
};
struct data edge[100000];
int check[10000],head[10000],cnt,dis[10000],n,m,s,inf=99999999;
void add(int x,int y,int z){
edge[++cnt].to=y;
edge[cnt].w=z;
edge[cnt].next=head[x];
head[x]=cnt;
}
int main(){
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<=n;i++){
dis[i]=inf;
}
for(int i=1;i<=m;i++){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
dis[s]=0;
int pos=s;
while(check[pos]==0){
int min=inf;
check[pos]=1;
for(int i=head[pos];i!=0;i=edge[i].next){
if(dis[edge[i].to]>dis[pos]+edge[i].w&&check[edge[i].to]==0){
dis[edge[i].to]=dis[pos]+edge[i].w;
}
}
for(int i=1;i<=n;i++){
if(dis[i]<min&&check[i]==0){
min=dis[i];
pos=i;
}
}
}
for(int i=1;i<=n;i++)
{
printf("%d ",dis[i]);
}
return 0;
}
这里我用到了1中所讲的链式前向星来储存图,迪杰斯特拉算法的时间复杂度为O(n*m),用堆优化后可以达到log级别,迪杰斯特拉算法的扩张性比弗洛伊德算法更强,但是迪杰斯特拉算法也有他的缺点那就是无法处理带有负权的图,果然十全十美的东西是不存在的(悲)。