• 最短路径算法


    诞生

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

    新概念

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

    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;
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    堆优化

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

    inline void Dijkstra(int u){
    	//初始化
    	queue<int>q;
    	memset(vis,false,sizeof(vis));
    	memset(dis,0x3f,sizeof(dis));
    	dis[u]=0;
    	vis[u]=true;
    	q.push(u);
    	//扩展
    	while(!q.empty()){
    		int x=q.front();
    		q.pop();
    		vis[x]=false;
    		//更新子节点
    		for(int i=head[x];i;i=e[i].nxt){
    			//判断 赋值并加入扩展新的节点的序列
    			if(dis[e[i].v]>dis[x]+e[i].w){
    			//赋值
    				dis[e[i].v]=dis[x]+e[i].w;
    				if(!vis[e[i].v]){
    				//加入预扩展队列,标记
    					vis[e[i].v]=true;
    					q.push(e[i].v);
    				}
    			}
    		}
    	}
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    最短路径证明

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

    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]);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    最短路证明

    问题来了,每次加点只能保证是两条边的最短路,会不会不包含呢?
    解答来了 {解答来了} 解答来了
    包含,假设有两个点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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    最短路证明

    与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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    总结

    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*算法,尽情期待

  • 相关阅读:
    嵌入式Linux入门-彻底理解UART串口,手把手教你写程序
    golang 切片结构体多条件排序
    Win 11 打开未知文件/打开方式 该文件没有与之关联的应用来执行该操作。请安装应用,若已经安装应用,请在“默认应用设置”页面中创建关联。
    【0143】 System V共享内存(Shared Memory)
    跳台阶算法
    OpenCV4之特征提取与对象检测
    聚观早报 | 苹果秋季发布会定档9月7日;​饿了么与抖音达成合作
    三分钟细数几款可视化前端开发工具
    【车载开发系列】HexView文件合并
    一本由红帽专家亲作的Quarkus实战型入门书籍——《Kubernetes原生微服务开发》
  • 原文地址:https://blog.csdn.net/weixin_42178241/article/details/125976389