IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 一篇文章 轻松搞懂 SPFA -> 正文阅读

[数据结构与算法]一篇文章 轻松搞懂 SPFA

概念

SPFA(Shortest Path Faster Algorithm) 和 Dijkstra算法 可谓是 单源最短路 的两大双子星了。

SPFA 由前身 Bellman-Ford-Moore(Bellman-Ford或BFM)算法添加一个 队列 来实现优化。
尽管如此,SPFA的复杂度最劣情况仍旧是BFM算法的复杂度,毕竟本质上只是多了一个优化的操作。

BFM算法

要知道,Richard Bellman(理查德.贝尔曼)是 动态规划 的创始人。
而BFM算法,正是运用了动态规划的思想。
相较于采用贪心思想的Dijkstra算法的无法处理 负权边
动态规划显然没有这个限制。

松弛

那么状态是怎么转移的呢?
我们记从源点到每个节点i的距离为 dis[i](f[i])。(dis=distance 距离)

对于已知的当前节点i的的dis[i],记其要转移的下一个节点j,有边权w[i][j]。(w=weight 权值)

那么显然有 dis[j] = min( dis[i]+w[i][j] , dis[j] )

其实介个操作就叫 松弛 啦。

而且,当我们发现对于每个点都没法更优时,说明我们已经得到了最优解,
那么我们可以直接跳出,以优化时间。

初始化

由于我们需要的是最小值
那么除了源点本身,其余都应该初始化为 + 无穷大(INF)
而源点本身到自己的距离自然是0

时间复杂度

显然我们需要枚举除了终点的每个 i 和 j,
故其 时间复杂度 大概为 节点数N乘以边数M,即 O(NM)

SPFA

进行优化

我们说过了,我们可以对BFM算法加入一个队列来进行优化。

我们可以在松弛时,如果该点j可以使得当前路径更短,并且其不在队列中,再将其加入队列

因为,如果其已在队列中,我们对其再加入队列的访问是多余的
因为,如果后面可以继续使得路径更短,它就已经会被加入队列

所以,我们考虑加入一个 vis[i] 来表示其是否在队列中。(vis=visited 已访问)

所以对于该点,如果有负环,显然对其的访问次数会超过N(节点数)。

那么我们可以考虑加入一个count[i],统计每个点的访问次数,一旦超过N,说明该图存在负环。

显然,如果存在负环,那么最后最短路的结果会是负无穷。。。

于是乎,对于存在负环时,我们使函数返回 0

模板代码

SPFA

这里大概打一下,不保证你copy过去能过。
模板还是自己打最好啦,大佬们的代码比我更好,毕竟你大佬还是你大佬。

bool SPFA(int start) 当图存在负环时,我们返回 0
{
	memset(dis,127,sizeof(dis)); 初始化dis数组为最大值
	dis[start]=0; 初始化时,源点与自身距离为 0
	memset(vis,0, ,sizeof(vis)); 初始化vis数组为 0
	vis[start]=true; 初始化时,源点入队并标记:标记
	
    queue<int> Q; 储存访问节点的队列
	Q.push(start); 初始化时,源点入队并标记:入队
	
    while(!Q.empty()) 没有可访问节点时结束
	{
        int i=Q.front();Q.pop();
        for(int j=1;j<=N;++j)
        {
            if(dis[j]>dis[i]+w[i][j]) 松弛
            {
                dis[j]=dis[i]+w[i][j];
                if(!vis[j]) 未在队列中则加入,下次访问
                {
                	vis[j]=true, 标记自身已入队
                    Q.push(j); 入队
                    if(++count[j]>N) return false; 计数,并检测负环
                }        
	        }            
        }
        vis[i]=false; 出队时,删除入队标记           
    }
    return true; 不存在负环,此时dis距离也已经处理好了
}

BFM算法

bool Bellman_Ford(int start) 同样有负环返回 0
{
    memset(dis,127,sizeof dis);
    dis[start]=0;
    int u,v,w;
    for(int i=1;i<N;++i) 对于每个点(除了终点,这里默认它为点n)
        for(int j=1;j<=M;++j) 枚举每条边,这里的edges储存每条边的出、入、权
		{
            u=edges[j].u, v=edges[j].v, w=edges[j].w;
            ij被for用了,我们用uv来代替下
            dis[v]=max(dis[v],dis[v]>dis[u]+w);
        }
    bool pd=true; 判断是否含有负权回路。
    			  如果我们最后已经理论上取得了最优解的情况下,发现还不够。
    			  说明不是我们的问题,而是有负环。
    for(int i=1;i<=M;++i)
        if(dis[edges[i].v]>dis[edges[i].u]+edges[i].w)
		{pd=false;break;}
    return pd;
}
  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-08-17 01:30:19  更:2021-08-17 01:30:46 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 21:25:37-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码