图论-最短路算法(dj配图)

本文介绍了Floyd算法、Bellman-Ford算法和Dijkstra算法在求解最短路径问题中的特点、适用范围和代码实现。Floyd算法适合多源问题,时间复杂度高;Bellman-Ford处理负权边且能检测环,时间复杂度O(n*m);Dijkstra专用于正权图,时间复杂度O(mlogm)。
摘要由CSDN通过智能技术生成

Floyd算法

算法特点

首先请出的是最短路算法中的最容易实现的算法Floyd算法,其解决的问题为多源最短路,即图上任意两点间的最短路问题。
floyd算法的时间复杂度较高,但换来的是较易的实现和较小的常数

适用范围

适用于任何图,不管有向无向,边权正负,但是最短路必须存在。(不能有个负环)(来自oiwiki)

算法实现

设数组f[x][y]表示从x到y的最短路,每次分别枚举中间点k,起点x,终点y,于是f[x][y]可以由min(f[x][k]+f[k][y])转移出来。

对于该方程有的同学可能有些疑惑,枚举起点x和y的顺序因为是对称的对答案没有影响,但枚举中间点k是否对答案有影响呢?
仔细分析每个枚举过程,可知每个枚举的中间点k一定可以松弛出前k-1个已经枚举过后的最短路,即第k个中间点的松弛操作是完全建立在前k-1个中间点的松弛操作后的,该松弛操作可以求出目前能松弛的所以最短路,所以对于每条最短路,其中一定有最后一个点x能经过松弛操作链接两条最短路,所以不必担心枚举k顺序对答案的影响

回到实现可知该算法需要枚举每个中间点,每个起点,终点,所以有三层循环,时间复杂度为O(n3)

代码实现

for(int k=1;k<=n;++k){//枚举中间点
    for(int x=1;x<=n;++x){//枚举起点
        for(int y=1;y<=n;++y){//枚举终点
              d[x][y]=min(d[x][y],d[x][k]+d[k][y]);
        }
    }
}

Bellman-Ford算法

算法特点

是基于松弛操作的算法,可以实现单源最短路的求取,能判断图是否含有负环

适用范围

可以适用于含有负边权的图,并能够判断最短路是否存在(是否有负环)

算法实现

松弛操作: dis[u]=min(dis[u],dis[v]+val[v]) 即对每一条路进行松弛操作,看其是否可以通过另一个更小的点转移过来。这是很好理解的,每次遍历整个图,如果有边能松弛就进行松弛操作,最后无法松弛时得到的就是最短路。

负环判断:考虑每次进行松弛操作,如果成功松弛,那么对于一条最短路会增加至少一条边,而一条最短路最多有n-1条边,所以如果松弛操作成功进行了n轮及以上,说明该图含有负环。

考虑时间复杂度,每次遍历的时间复杂度为O(m),如果没有负环的情况下最多遍历n次,总的时间复杂度为O(n*m)

代码实现

struct edge{
    int u,v,val;
    // u起点
    // v终点
    // val边权
}
edge e[m];
const int inf=0x3f3f3f3f;
bool bellman-ford(){
     bool flag = false; //判断该轮松弛操作是否成功
     memset(dis,0x3f,sizeof(dis)); //初始化所有dis
     dis[st] = 0;//起点的dis设为0
     for(int i=1;i<=n;++i){ //进行n轮松弛操作
         flag = false;
         for(int i=1;i<=m;++i){ //遍历每条边
             int u=e[i].u,v=e[i].v,val=e[i].val;
             if(dis[u]==inf) continue;//如果该点未被松弛,自然也无法松弛其他点
             if(dis[v]>dis[u]+val){
                 flag = true; //松弛成功
                 dis[v]=dis[u]+val; //更新最短路
             }
         }
         if(!flag){ //如果该轮没有松弛成功,证明已经找出所有最短路,可以跳出循环
             break;
         }
     }
     return flag;// 如果flag为flase证明成功找到最短路,图内没有负环,如果flag为true证明第n次操作同样松弛成功,根据上面的证明可知,该图内含有负环.
}

算法优化:SPFA

根据上面的松弛公式不难推出,只有在上一轮被松弛过的终点v才有可能去松弛其他的点,所以我们只要通过队列来存储上次松弛过的节点即可减去不必要边的遍历,但由于卡spfa已成常态,而在最坏情况下spfa的时间复杂度接近于普通的bellman-ford,所以常有spfa已死的说法,在这里就不继续展开spfa算法了。

Dijkstra 算法

算法特点

是求正边权图的单源最短路最常用的算法,而且名字容易叫错

适用范围

是这次介绍算法中唯一一个不能用于有负边权的图的算法

算法实现

1.将已经标记节点舍弃,找出剩下点中,dis最小的点,加入队列
2.对该边终点的所有连边进行松弛操作,并标记该节点
以下面图的例子为例
在这里插入图片描述
首先将除了起点外的所有点的dis设为正无穷,并将起点1加入队列
更新1所有连边的dis值
在这里插入图片描述
此时dis[2] 变为1,dis[3]变为3,给节点1打上标记。
通过分析dis可知,剩余点中dis值最小的点为节点2.

将2放入队列,更新2所有连边的dis值
在这里插入图片描述
接下来发现dis值最小的点为3,将3放入队列,更新节点3所有连边的dis值
在这里插入图片描述
最后发现dis值最小的点是节点4,同理对节点4进行操作。
最后所有点都打上标记,没有可以松弛的边,算法结束。

因为每次的dis值是由另一节点的dis值转移过来,所以可证,每次dis最小的节点到源点的最短路已经找出,所以可由他去松弛其他节点

之所以dj算法不能适用于有负边权的图就是因为,在负边权的图中,每次dis最小的节点到源点的最短路不一定找出,可能由更大的dis加上一个负值得出

考虑使用优先队列来维护每次的最小dis值,队列中的元素最多为m个,会进行m次插入删除操作,而优先队列的时间复杂度为O(logm),所以总的时间复杂度为O(mlogm) (这里只给出了优先队列的优化方式,其他优化请参考oiwiki)

代码实现

struct node{
	int dis,u;// dis距离 节点 
	bool operator<(const node& a)const{//优先队列小根堆 
	    return a.dis<dis;
	}
}
vector<pair<int,int>>e[N];//存边 
int dis[N];
priority_queue<node>q;//优先队列 
void dijkstra(){
	memset(dis,0x3f,sizeof(dis)) //初始化所有dis
	dis[s]=0; //起点的dis设为0 
	q.push({0,s}); //放入起点 
	while(q.size()){
		int u=q.top().u;// 每次选出dis最小的节点 
		q.pop();
		if(vis[u]) continue; //如果标记过该点就退出
		vis[u]=1;
		for(auto i:e[u]){// 对该点的所有连边进行松弛操作 
			int val=i.first,v=i.second;//边权 边的终点  
			if(dis[v]>dis[u]+val){ //松弛成功,将新的dis加入队列 
				dis[v]=dis[u]+val;
				q.push({dis[v],v});
			}
		} 
	}
}
  • 21
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值