最短路径算法

诞生

之前讲解了BFS&DFS基本场景与演化。
(ps:https://blog.csdn.net/weixin_42178241/article/details/12595659)
最短路的诞生是因为每条边有多了一个权值,不能用BFS,DFS按照层次去算了。

新概念

最短路径还是图论的一种,和BFS,DFS一样有三种建图方式,不过我们引入了3个新概念。

  1. 源点,从a->b的最短路,a为源点
  2. 边权,边的权值,u->v有一条边,那边权就是w(u,v),意义可以定义
  3. 松弛,主要用于扩展刚求出一点最短路径去扩展其他子节点的操作(后面会解释)

方法

最短路径的求法思路有4大种,代码优化实现总共5种

Dijkstra

单源最短路(只能算从一个源点开始,到其他节点的最短路)

思想

每个节点的状态分3种,已知最短路,求出短路不确定是否最短,未求
我们首先将所有点赋值无穷大,用dis[i]表示源点到i的最短路径,dis[s]=0(s源点)
从源点开始向外扩展没有向外扩展的节点更新其他节点的最短路,分别后续扩展
最后dis的值就ok了。

完美图解

抖音号:向往星辰大海的white_hacker的视频中有

代码实现

无优化
void dijkstra(int s){
	for(int i=1;i<=n;i++) dis[i]=inf; // dis用来存储源点到任意结点的最短路,初始值无穷大
	dis[s]=0; // 源点到自身的最短路显然为 0
	for(int i=1;i<n;i++){ // 每次扩展一个结点,除源点外共有n-1个结点待扩展
		int u,cost=inf;
		// 找到距离孤岛最近的结点
		for(int j=1;j<=n;j++){
			if(!vis[u]&&dis[j]<cost){
				cost=dis[j];
				u=j;
			}
		}
		vis[u]=true; // u 标记为已访问
		// 对 u 进行扩展
		for(int j=head[u];j;j=edge[j].nxt){
			int v=edge[j].v,w=edge[j].w;
			if(!vis[v]&&dis[v]>dis[u]+w)dis[v]=dis[u]+w;
		}
	}
}
优先优化

优先优化 实际上与无优化的Dijkstra的思路一样,只是在每次扩展点的时,加以数据结构对时间复杂度的优化。

struct node{
	int ds,op;
	bool operator>(const node& a)const {return ds>a.ds;}
};
priority_queue< node,vector<node>,greater<node> >q;
struct edge{
	int nxt,v;
	ll w;
}a[maxm];
inline void add(int u,int v,ll w){
	a[++cnt].v=v;
	a[cnt].w=w;
	a[cnt].nxt=head[u];
	head[u]=cnt; 
}
inline void dijkstra(){
	dis[s]=0;
	q.push({dis[s],s});
	while(!q.empty()){
		int x=q.top().op;
		q.pop();
		if(vis[x])continue;
		vis[x]=true;
		for(int i=head[x];i;i=a[i].nxt){
			if(dis[a[i].v]>dis[x]+a[i].w){
				dis[a[i].v]=(ll)dis[x]+a[i].w;
				if(!vis[a[i].v])q.push({dis[a[i].v],a[i].v});
			}
		}
	}
}

最短路径证明

每个节点只会经过一次扩展,每次扩展的都是在没有扩展过的且被标记过路径的最短,去扩展其他点,绝对性的保障了最短路径的最优解

Floyd

思想

动态规划,主要想看,i->j的最短路中间加一个k点,能否比之前路径短

代码实现

只有5行很简单

for(int k=1;k<=n;k++){
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++)d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
	}
}

最短路证明

问题来了,每次加点只能保证是两条边的最短路,会不会不包含呢?
解答来了 {解答来了} 解答来了
包含,假设有两个点u,v,k,且u,v之间有一条边,这时我们开始加点了u->k,k->v,算在了d[u][v]里,之后继续调用它,得到证明。

Bellman-ford

思想

Bellman-ford算法,主要是在松弛边,共松弛n-1次,如果还能松弛,证明此图有环.

代码实现

inline bool Bellman_ford(){
//初始化
	memset(dis,0,sizeof(dis));
	dis[s]=v;
	//n-1次松弛
	for(int i=1;i<n;i++){
		bool flag=false;
		//找边松弛
		for(int j=1;j<=cnt;j++){
		//判断赋值
			if(dis[e[j].b]<(dis[e[j].a]-e[j].c)*e[j].r){
				dis[e[j].b]=(dis[e[j].a]-e[j].c)*e[j].r;
				flag=true;
			}
		}
		if(!flag)return false;
	}
	//还可以松弛 ==》(有环图)
	for(int i=1;i<=cnt;i++){
		if(dis[e[i].b]<(dis[e[i].a]-e[i].c)*e[i].r)return true;
	}
	return false;
}

最短路证明

与Dijkstra相反,每个点可以多次扩展,每次保证用最小值扩展其他子节点,可以保证最短路,还可以判断负环。

SPFA(Bellman-ford的队列优化)

思想

和Bellman-ford一样;

代码实现

bool spfa( int s ){
	for ( int i = 1; i <= n; i++ )
		dis[i] = inf;   /* 初始化操作 */
	queue<node> q;
	dis[s]=0;
	in[s]= true;         /* 标记结点是否在队列中 */
	cnt[s]++;               /* 记录结点进入队列的次数 */
	q.push( s );
	while ( q.size() )
	{
		int u = q.front(); q.pop();
		in[u] = false;
		for ( int j = head[u]; j; j = edge[j].nxt )
		{
			int v = edge[j].v, w = edge[j].w;
			if ( dis[v] > dis[u] + w ) /* 对相邻的每条边进行松弛 */
				dis[v] = dis[u] + w;
			if ( in[v] )
				continue;
			q.push( v );
			in[v] = true;
			cnt[v]++;
			if ( cnt[v] > n ){
				return(false); /* 存在负环 */
			}
		}
	}
	return(true);
}

总结

Dijkstra未优化

· 时间复杂度:O(n^2)
· 空间复杂度:O(m)
· 应用场景:单源最短路 边权均为正 无环图

Dijkstra堆优化

· 时间复杂度: O(m*log(m))
· 空间复杂度: O(m)
· 应用场景: 单源最短路 边权均为正 无环图

Floyd

· 时间复杂度:O(n^3)
· 空间复杂度:O(n^2)
· 应用场景:多源最短路 边权环都兼容

Bellman-ford

· 时间复杂度:O(n*m)
· 空间复杂度:O(m)
· 应用场景:单源最短路 边权环都兼容

SPFA

· 时间复杂度:O(n*m)
· 空间复杂度:O(m)
· 应用场景:单源最短路 边权环都兼容

后记

后面会讲解tarjan算法和A*算法,尽情期待

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

materialistOier

我只是一名ssfoier

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值