数据结构-图的遍历之Bellman-Ford算法和SPFA算法

一、Bellman-Ford算法

  1. 用于解决单源最短路径的问题,但也能够处理有负权边的情况。这是与Djikstra算法不同的地方。
  2. 关于复杂度,要比Djikstra的复杂度更高一点。O(VE),而Djikstra复杂度是O(V^2),V是点的数量,E是边的数量
  3. 原理,就是会出现负环的情况,会使得最短路径越来越小,进而产生错误;如果出现负环,源点无法到达,那么也是不会影响求解的。
  4. 设置d数组,用于存储最短路径的距离,如果存在可以到达的负环,那么返回false;如果不存在,那么数组d中存储的就是最短距离,返回true。
  5. 主要思想及伪代码:就是对于图中的边进行V-1轮操作。每一轮遍历所有的边,如果可以更新当前的距离,那么就进行更新;此时如果没有从源点到达的负环,那么数组d就是当前最优值,因此只需再对于所有边进行遍历一次,判断是否还有值可以更新,如果有,那么就说明图中存在源点可以到达的负环,返回false,如果没有那就返回true。
  6. 使用邻接表,如果使用邻接矩阵会使得算法的复杂度达到O(V^3)代码:
struct node{
    int v, dis;//v为邻接边的目标顶点,dis为邻接边的边权
};
vector<node> Adj[MAXN];
int n;
int d[MAXN];

bool Bellman_Ford(int s){
    fill(d, d+MAXN; INF);
    d[s] = 0;
    for(int i = 0; i < n-1; i++){
        for(int u = 0; u < n; u++){
            for(int j = 0; j < Adj[u].size(); j++){
                int v = Adj[u][j].v;
                int dis = Adj[u][j].dis;
                if(d[u] + dis < d[v]){
                    d[v] = d[u] + dis;
                }
            }
        }
    }
    //以下为判断负环的代码
    for(int u = 0; u < n; u++){
        for(int j = 0; j < Adj[u].size(); j++){
            int v = Adj[u][j].v;
            int dis = Adj[u][j].dis;
            if(d[u] + dis < d[v]){
                return false;
            }
        }
    }
    return true;
}
  • 需要注意点:统计最短路径条数的做法,由于Bellman-Ford算法期间会多次访问曾经访问过的结点,如果还是按照之前的写法,会统计已经出现过的结点,为了解决这个问题,使用记录 前驱结点的数组为set pre[MAXN],当遇见一条和已有最短路径长度相同路径时,必须重新计算最短路径的条数。

二、SPFA(Shortest Path Faster Algorithm)算法

  1. 这是对Bellman-Ford优化后产生的算法
  2. 我们知道每轮都需要操作每条边,其中存在很多无意义的边,所以我们知道只有当某个顶点的d[u]值发生改变的时候,从它出发的邻接点v的d[v]值才会发生改变
  3. 由此,可以进行优化,建立一个队列,每次将队首顶点u取出,然后对从u出发的所有边u->v进行松弛操作,如果没有获得最优的值就不管,如果获得最优值,v如果不在队列中,那么就添加进入队列。这样操作直到队列中没有结点(说明图中没有从源点可达的负环), 或是某个顶点的入队次数超过V-1(说明图中存在从源点可达的负环)。
  4. 这种优化后的算法期望时间复杂度是O(kE),k是一个图的常数,E是图的边数。但是如果图中存在源点可以到达的负环,那么传统的SPFA的时间复杂度退化成O(VE)。
  5. 如果事先知道图中不会有环,那么num数组的部分可以去掉。
  6. 如果负环从源点不可达,则需要添加辅助顶点C,并添加一条从源点到达C的有向边以及V-1条从C到达除源点外各顶点的有向边才能判断负环是否存在。思考为啥
  • 下面给出SPFA的代码:
struct node{
    int v, dis;//v为邻接边的目标顶点,dis为邻接边的边权
};
vector<node> Adj[MAXN];
int n, d[MAXN], num[MAXN];//num数组为记录顶点入队列次数
bool inq[MAXV];//顶点是否在队列中

bool SPFA(int s){
    //初始化
    memset(inq, false, sizeof(inq));
    memset(num, 0, sizeof(num));
    fill(d, d+MAXN, INF);
    //源点入队列部分
    queue<int> Q;
    Q.push(s);
    inq[s] = true;
    num[s]++;
    d[s] = 0;
    //主体部分
    while(!Q.empty()){
        int u = Q.front();
        Q.pop();
        inq[u] = false;
        //遍历u所有邻接边v
        for(int j = 0; j < Adj[u].size(); j++){
            int v = Adj[u][j].v;
            int dis = Adj[u][j].dis;
            //松弛操作
            if(d[u] + dis < d[v]){
                d[v] = d[u] + dis;
                if(!inq[v]){
                    Q.push(v);
                    inq[v] = true;
                    num[v]++;
                    if(num[v] >= n) return false;
                }
            }
        }
    }
    return true;
}

原文地址:https://www.cnblogs.com/tsruixi/p/12400612.html

时间: 2024-08-04 08:03:43

数据结构-图的遍历之Bellman-Ford算法和SPFA算法的相关文章

C#与数据结构--图的遍历

C#与数据结构--图的遍历 8.2 图的存储结构 图 的存储结构除了要存储图中各个顶点的本身的信息外,同时还要存储顶点与顶点之间的所有关系(边的信息),因此,图的结构比较复杂,很难以数据元素在存储区 中的物理位置来表示元素之间的关系,但也正是由于其任意的特性,故物理表示方法很多.常用的图的存储结构有邻接矩阵.邻接表.十字链表和邻接多重表. 8.2.1  邻接矩阵表示法 对于一个具有n个顶点的图,可以使用n*n的矩阵(二维数组)来表示它们间的邻接关系.图8.10和图8.11中,矩阵A(i,j)=1

数据结构-图的遍历

图的遍历是指从一个顶点出发,访问且仅一次访问图中其余所有顶点,不是所有边的处理.是求图的连通性,拓扑排序,路径求解等问题的基础. 非常基本的图的遍历方法有深度优先搜索法和广度(宽度)优先搜索法. 深度优先搜索,Depth First Search,DFS 深度优先搜索法是树的先根遍历的推广,它的基本思想是:从图G的某个顶点v0出发,访问v0,然后选择一个与v0相邻且没被访问过的顶点vi访问,再从vi出发选择一个与vi相邻且未被访问的顶点vj进行访问,依次继续.如果当前被访问过的顶点的所有邻接顶点

使用Apriori算法和FP-growth算法进行关联分析(Python版)

===================================================================== <机器学习实战>系列博客是博主阅读<机器学习实战>这本书的笔记也包含一些其他python实现的机器学习算法 算法实现均采用python github 源码同步:https://github.com/Thinkgamer/Machine-Learning-With-Python ==================================

最短路径Dijkstra算法和Floyd算法整理、

转载自:http://www.cnblogs.com/biyeymyhjob/archive/2012/07/31/2615833.html 最短路径—Dijkstra算法和Floyd算法 Dijkstra算法 1.定义概览 Dijkstra(迪杰斯特拉)算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径.主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止.Dijkstra算法是很有代表性的最短路径算法,在很多专业课程中都作为基本内容有详细的介绍,如数据结构,图论,运筹

Prim算法和Kruskal算法的正确性证明

今天学习了Prim算法和Kruskal算法,因为书中只给出了算法的实现,而没有给出关于算法正确性的证明,所以尝试着给出了自己的证明.刚才看了一下<算法>一书中的相关章节,使用了切分定理来证明这两个算法的正确性,更加简洁.优雅并且根本.相比之下,我的证明带着许多草莽气息,于此写成博客,只当是记录自己的思考 ------------------------------------------- 说明: 本文仅提供关于两个算法的正确性的证明,不涉及对算法的过程描述和实现细节 本人算法菜鸟一枚,提供的

最小生成树-Prim算法和Kruskal算法

原文链接:http://www.cnblogs.com/biyeymyhjob/archive/2012/07/30/2615542.html Prim算法 1.概览 普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树.意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点(英语:Vertex (graph theory)),且其所有边的权值之和亦为最小.该算法于1930年由捷克数学家沃伊捷赫·亚尔尼克(英语:Vojtěch Jarník)发现:并在195

使用Apriori算法和FP-growth算法进行关联分析

系列文章:<机器学习>学习笔记 最近看了<机器学习实战>中的第11章(使用Apriori算法进行关联分析)和第12章(使用FP-growth算法来高效发现频繁项集).正如章节标题所示,这两章讲了无监督机器学习方法中的关联分析问题.关联分析可以用于回答"哪些商品经常被同时购买?"之类的问题.书中举了一些关联分析的例子: 通过查看哪些商品经常在一起购买,可以帮助商店了解用户的购买行为.这种从数据海洋中抽取的知识可以用于商品定价.市场促销.存活管理等环节. 在美国国会

Dijkstra算法和Floyd算法的正确性证明

说明: 本文仅提供关于两个算法的正确性的证明,不涉及对算法的过程描述和实现细节 本人算法菜鸟一枚,提供的证明仅是自己的思路,不保证正确,仅供参考,若有错误,欢迎拍砖指正 ------------------------------------------- Dijkstra算法和Floyd算法用于求解连通图中任意两个顶点之间的最短路径 Dijksra算法从一个顶点v0出发,每次为一个顶点vi确定到达v0的最小路径 Dijkstra算法用distance[i]记录顶点vi到v0的最短路径,用pat

最小生成树(Prim算法和Kruskal算法)

1)最小生成树 给定一个无向图,如果它的某个子图中任意两个顶点都互相连通并且是一棵树,那么这棵树就叫生成树.如果边上有权值,那么使得边权和最小的生成树叫做最小生成树(MST,Minimum Spanning Tree) 2)应用 比如让你为一个镇的九个村庄架设通信网络,每个村庄相当于一个顶点,权值是村与村之间可通达的直线距离,要求你必须用最小的成本完成这次任务:或者村庄之间建公路,连通N个村庄要N-1条路,如何让建路成本最低之类的问题. 1.Prim算法 ①该算法是构建最小生成树的算法之一.它是