Bellman-Ford算法cpp算法导论原理实现

39 篇文章 3 订阅
11 篇文章 0 订阅
Bellman-Ford算法

该算法的特性是针对更广泛情况下的最短路径查询,其中边权可以为负数,并且会针对数据判断图中是否有从源节点到达负权环路的路径。

方法依旧基于Class<Graph>实现,其中对少数地方做了对应修正,在带权边最短路算法中需要两个最基本的操作:

  • 初始化操作:使得除了根节点以外的节点d属性设置为 ∞ \infty ,前驱节点设置为空,这里的d属性表示的是该节点距离源节点的上界或标准最小值;

    void Graph::InitializeSingleSource(string s)
    {
        for (auto &i : G)
        {
            i._d = Maxnum;
            i._pi = nullptr;
        }
        G[index[s]]._d = 0;
    }
    
  • 松弛操作:对于一条有向边(u,v),其边权不变化,但如果出边点距离发生优化,则入边点就需要及时更新:

    void Graph::Relax(string u, string v)
    {
        if (G[index[v]]._d > G[index[u]]._d + weight[{u, v}])
        {
            G[index[v]]._d = G[index[u]]._d + weight[{u, v}];
            G[index[v]]._pi = &G[index[u]];
        }
    }
    

上述两个基本操作组成了Bellman-Ford算法的基本要素,之后要进行的就是组装。

由于上述提到的其算法可以检测是否存在负权环边,那么函数的就会被定义为布尔类型,此外检测负权环边要根据其特性:无限循环的情况下会被无限优化至负无穷(所以说负无穷的逻辑定义也是数值上定义同步的),最终算法过程被描述为:

bool Graph::BellmanFord(string root)
{
    InitializeSingleSource(root);
    for (int i = 1; i < vertex; i++)
        for (auto it : weight)
            Relax(it.first.first, it.first.second);
    for (auto it : weight)
        if (G[index[it.first.second]]._d > G[index[it.first.first]]._d + it.second)
            return false;
    return true;
}

主要分为三个步骤:

  • 初始化;
  • ∣ V ∣ − 1 |V|-1 V1次遍历所有边并松弛;
  • 结束松弛后判断是否能继续被松弛,若可以则证明其存在负权环边;

并且该算法依旧采用了临界链表储存,而边值为了便于操作选择了map<pair<str,str>,int>实现,这里为了便于实现而并不代表实际性能。

在这里插入图片描述

采用图中数据做测验,输出点的d属性为:

s 0
t 2
x 4
y 7
z -2
分析
算法时间复杂度

从上述描述可以得出算法主要消耗在 ∣ V ∣ − 1 |V|-1 V1次对所有边的遍历以及常数的松弛操作,所以Bellman-Ford算法的时间复杂度为 O ( V E ) O(VE) O(VE)

算法正确性

引理: G = ( V , E ) G=(V,E) G=(V,E)为一个带权且源节点为 s s s的有向图,若图中不包含负权环路,则算法进行 ∣ V ∣ − 1 |V|-1 V1次后对于所有其他从源节点出发的点v v . d = δ ( s , v ) v.d=\delta(s,v) v.d=δ(s,v)

考虑路径 p p p为源节点到v的最短路径:
p = < v 0 , v 1 , . . . , v k > p=<v_0,v_1,...,v_k> p=<v0,v1,...,vk>
上节所提到的最短路均不包含环路(最短的意义扩展到边的数量上),所以 ∣ p ∣ ≤ ∣ V ∣ − 1 |p|\le |V|-1 pV1,由此在图中优化 ∣ V ∣ − 1 |V|-1 V1次保证了最远的点的所有边都被遍历优化过,结果成立。

推论:在上述的图的前提下,存在一条从源节点 s s s到任意节点 v v v的路径当且仅当算法终止时 v . d < ∞ v.d< \infty v.d<

假设有一条最短路径存在,那么根据上述引理,最短路径一定存在且被松弛到非无穷的数值范围内;

反过来看如果存在 v . d < ∞ v.d< \infty v.d<,那么在此过程中一定被某个相邻的点进行过松弛优化,所以该点给v的前驱节点,在最短路搜索树中根节点为s,既然在搜索过程中被更新说明根节点是v的前驱的祖先,那么同样也是v的祖先,所以存在一条最短路径。

[定理]Bellman-Ford的正确性:依旧在上述图的背景下,若图中不存在负权环路则返回True且对于所有节点的前驱组成的前驱子图是一棵最短路径树;相反若存在负权环路则算法返回False

当图中不包含负权环路时,上述引理证明了算法终止时,源节点到某一节点v可达状态下存在 v . d = δ ( s , v ) v.d=\delta(s,v) v.d=δ(s,v),不可达其同样满足(值为 ∞ \infty )。所以在上述要求下,前驱子图确定是一棵最短路径树,同时在最终检验中,因为不存在负权环路所以没有边会在检验中被优化,最终算法返回True

当图中包含一条负权环路:
p = < v 0 , v 1 , . . . , v k > p=<v_0,v_1,...,v_k> p=<v0,v1,...,vk>
同时 v 0 = v k v_0=v_k v0=vk,则会得出结论:
∑ i = 1 k w ( v i − 1 , v i ) < 0 \sum_{i=1}^{k} w\left(v_{i-1}, v_{i}\right)<0 i=1kw(vi1,vi)<0
假设最终返回了True,则一定保证不会再检验时跳出换句话说就是所有的边满足了:
v i . d ≤ v i − 1 . d + w ( v i − 1 , v i ) v_{i} . d \leq v_{i-1} . d+w\left(v_{i-1}, v_{i}\right) vi.dvi1.d+w(vi1,vi)
对其进行加和处理:
∑ i = 1 k v i ⋅ d ≤ ∑ i = 1 k ( v i − 1 ⋅ d + w ( v i − 1 , v i ) ) = ∑ i = 1 k v i − 1 ⋅ d + ∑ i = 1 k w ( v i − 1 , v i ) \begin{aligned} \sum_{i=1}^{k} v_{i} \cdot d & \leq \sum_{i=1}^{k}\left(v_{i-1} \cdot d+w\left(v_{i-1}, v_{i}\right)\right) \\ &=\sum_{i=1}^{k} v_{i-1} \cdot d+\sum_{i=1}^{k} w\left(v_{i-1}, v_{i}\right) \end{aligned} i=1kvidi=1k(vi1d+w(vi1,vi))=i=1kvi1d+i=1kw(vi1,vi)
可以明显看出两个 v . d v.d v.d的加和函数是错位的,但是由于 v 0 = v k v_0=v_k v0=vk,那么上式可以抵消两个对属性d的求和函数得到:
0 ≤ ∑ i = 1 k w ( v i − 1 , v i ) 0 \leq \sum_{i=1}^{k} w\left(v_{i-1}, v_{i}\right) 0i=1kw(vi1,vi)
可以看到与最初的结论相矛盾,所以该问题得证。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
仅仅是作为搬运工。 算法精粹——举一反三,抛弃题海战术 本书的目标读者是准备去硅谷找工作的码农,也适用于在国内找工作的码农,以及刚接触ACM算法竞赛的新手。 市场上讲解算法的书已经汗牛充栋,为什么还要写这本书呢?主要原因是我对目前市场上的大部分算法书都不太满意。 本书有如下特色: 背后有强大的AlgoHub支持。 本书的所有题目,都可以在 www.algohub.org(即将上线) 上在线判断代码。这样的一大好处是,读者可以边看书,边实现自己的代码,然后提交到网站上验证自己的想法是否正确。AlgoHub的使命是成为最好的算法学习和交流平台。AlgoHub囊括了 POJ, ZOJ, leetcode, HackerRank 等网站的经典题目(一些质量不高的题目则忽略),且 AlgoHub有非常简单的加题系统,用户不需要写一行代码即可自己添加题目,所以AlgoHub的题库还在飞速增长中。 每道题都有完整的代码。 市场上的大部分书,都会讲思路,但给出的代码都是片段,不是完整可编译的代码。本书每题都有完整的代码,且每个代码经过千锤百炼,保证可读性的前提下尽可能简短,方面读者在面试中能快速写出来。 每道题都有多种解法。 本书的宗旨是,用尽可能少的题目,覆盖尽可能多的算法。本书中的的每道题都有多种解法,每种解法不是简单的小改进,而是完全不同的思路,力求举一反三,让读者触类旁通。 本书支持多种主流编程语言。 目前支持 Java, C++, C#, Python, Ruby, JavaScript, Swift, Scala, Clojure, 将来还会支持更多编程语言。 在线阅读 https://www.gitbook.com/book/soulmachine/algorithm-essentials/ 内容目录 介绍 线性表 数组 Remove Duplicates from Sorted Array Remove Duplicates from Sorted Array II Longest Consecutive Sequence Two Sum 3Sum 3Sum Closest 4Sum Remove Element Move Zeroes Next Permutation Permutation Sequence Valid Sudoku Trapping Rain Water Rotate Image Plus One Climbing Stairs Set Matrix Zeroes Gas Station Candy Majority Element Rotate Array Contains Duplicate Contains Duplicate II Contains Duplicate III Product of Array Except Self Game of Life Increasing Triplet Subsequence 单链表 Reverse Linked List Odd Even Linked List Add Two Numbers Reverse Linked List II Partition List Remove Duplicates from Sorted List Remove Duplicates from Sorted List II Rotate List Remove Nth Node From End of List Swap Nodes in Pairs Reverse Nodes in k-Group Copy List with Random Pointer Linked List Cycle Linked List Cycle II Reorder List LRU Cache Palindrome Linked List 字符串 Valid Palindrome Implement strStr() String to Integer (atoi) Add Binary Longest Palindromic Substring Regular Expression Matching Wildcard Matching Longest Common Prefix Valid Number Integer to Roman Roman to Integer Count and Say Anagrams Valid Anagram Simplify Path Length of Last Word Isomorphic Strings Word Pattern 栈和队列 栈 Min Stack Valid Parentheses L

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

数学小牛马

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值