BFS、SPFA、Dijkstra算法中vis数组的用法对比

2024-01-05 22:44

本文主要是介绍BFS、SPFA、Dijkstra算法中vis数组的用法对比,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

先上代码:

BFS:

bool vis[MAXN];
int dis[MAXN];void bfs(int s)
{queue<int> Q;Q.push(s);vis[s] = true;while(!Q.empty()){int u = Q.front();Q.pop();for(int e = first[u]; e; e = nxt[e]){int v = go[e];if(vis[v]) continue;dis[v] = dis[u] + 1;vis[v] = true;Q.push(v);}}
}

SPFA:

int dis[MAXN];
bool vis[MAXN];void SPFA(int s)
{for(int i = 1; i <= n; ++i) dis[i] = inf;dis[s] = 0;queue<int> Q;Q.push(s);vis[s] = true;while(!Q.empty()){int u = Q.front();Q.pop();vis[u] = false;for(int e = first[u]; e; e = nxt[e]){int v = go[e];if(dis[v] > dis[u] + val[e]){dis[v] = dis[u] + val[e];if(!vis[v]){Q.push(v);vis[v] = true;}}}}
}

Dijkstra:

struct node
{int u, ds;friend bool operator<(const node& n1, const node& n2){return n1.ds > n2.ds;}
};bool vis[MAXN];
int dis[MAXN];void Dijkstra(int s)
{memset(dis, 0x3f, sizeof(dis));dis[s] = 0;priority_queue<node> Q;Q.push({s, 0});while(!Q.empty()){int u = Q.top().u;Q.pop();if(vis[u]) continue;vis[u] = true;for(int e = first[u]; e; e = nxt[e]){int v = go[e];if(dis[v] > dis[u] + val[e]){dis[v] = dis[u] + val[e];if(!vis[v]) Q.push({v, dis[v]});}}}
}

分析对比

在BFS算法中

当Q.push()调用时顺便设置vis数组。原因是,BFS只能应用于边权为1的有向无环图(Directed Acyclic Graph, DAG),节点v第一次被搜索到时的dis值就是s→v的最短路。因此,在后面又访问到v时,根本不需要干任何事情,既不需要加入队列,也不需要更新dis值。因此,vis数组确保每个点只入队一次、dis值只被更新一次。

在SPFA(队列优化的Bellman-Ford算法)中

vis数组标记了节点v是否在Q中。元素出队时vis设置为false,入队时设置为true。如果节点v已经在队列中,则不需要再次入队。SPFA可以看作时BFS的衍生算法,它可以用于任意图,包括多重图、含有自环的图。对于已经访问过的节点,如果形成环路,后面的节点可以更新前面节点的dis值。其实可以去掉vis数组,但是这样会导致某一时刻队列中包含多个同一元素,极大地降低了算法的效率。因为同一时刻队列中有多个同一元素和只有一个该元素的效果是一样的。

Dijkstra算法是一个较为复杂的算法。根据《算法导论》

Dijkstra算法在运行过程中维持的关键信息是一组结点集合S。从源结点s到该集合中每个结点之间的最短路径已经被找到。算法重复从结点集V-S中选择最短路径估计最小的结点u,将u加入到集合S,然后对所有从u出发的边进行松弛。(P383)
算法维持的不变式为Q=V-S。(P384)(注:V是所有结点的集合,Q是优先队列,S为已经求出最短路的结点集合)
Dijkstra的暴力算法会维持该循环不变式。但我们的算法并不会,因为只有被松驰过的结点才有可能加入队列。理由是,没有被松驰过的结点其dis值一定是inf。Dijkstra还满足一个定理
定理 队列Q中dis值最小的结点u,其dis值就是s→u的最短路。
因此,我们的vis数组的意义是标志结点u是否属于集合S,即结点u的最短路是否已经被计算出来。当u出队时,根据定理,其最短路一定已经被计算出来了,因此标记vis[u]=true。在这之前,我们写道:
if(vis[u]) continue;
不加这句话会TLE。原因何在?为什么同一个结点会出队两次?因为它可能入队两次。或者说,它出队之前被连续松弛了超过一次,导致它在队里有好几个。因此我们使用vis数组,保证不走冤枉路。(亲测,if(!vis[v]) Q.push({v, dis[v]})中if(!vis[v])可以去掉)
区别Dijkstra和SPFA的一个方法是:Dijkstra末有4个右花括号,SPFA有5个

这篇关于BFS、SPFA、Dijkstra算法中vis数组的用法对比的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/574393

相关文章

mysql中insert into的基本用法和一些示例

《mysql中insertinto的基本用法和一些示例》INSERTINTO用于向MySQL表插入新行,支持单行/多行及部分列插入,下面给大家介绍mysql中insertinto的基本用法和一些示例... 目录基本语法插入单行数据插入多行数据插入部分列的数据插入默认值注意事项在mysql中,INSERT I

mapstruct中的@Mapper注解的基本用法

《mapstruct中的@Mapper注解的基本用法》在MapStruct中,@Mapper注解是核心注解之一,用于标记一个接口或抽象类为MapStruct的映射器(Mapper),本文给大家介绍ma... 目录1. 基本用法2. 常用属性3. 高级用法4. 注意事项5. 总结6. 编译异常处理在MapSt

java中long的一些常见用法

《java中long的一些常见用法》在Java中,long是一种基本数据类型,用于表示长整型数值,接下来通过本文给大家介绍java中long的一些常见用法,感兴趣的朋友一起看看吧... 在Java中,long是一种基本数据类型,用于表示长整型数值。它的取值范围比int更大,从-922337203685477

MyBatis ResultMap 的基本用法示例详解

《MyBatisResultMap的基本用法示例详解》在MyBatis中,resultMap用于定义数据库查询结果到Java对象属性的映射关系,本文给大家介绍MyBatisResultMap的基本... 目录MyBATis 中的 resultMap1. resultMap 的基本语法2. 简单的 resul

Python主动抛出异常的各种用法和场景分析

《Python主动抛出异常的各种用法和场景分析》在Python中,我们不仅可以捕获和处理异常,还可以主动抛出异常,也就是以类的方式自定义错误的类型和提示信息,这在编程中非常有用,下面我将详细解释主动抛... 目录一、为什么要主动抛出异常?二、基本语法:raise关键字基本示例三、raise的多种用法1. 抛

MySQL JSON 查询中的对象与数组技巧及查询示例

《MySQLJSON查询中的对象与数组技巧及查询示例》MySQL中JSON对象和JSON数组查询的详细介绍及带有WHERE条件的查询示例,本文给大家介绍的非常详细,mysqljson查询示例相关知... 目录jsON 对象查询1. JSON_CONTAINS2. JSON_EXTRACT3. JSON_TA

java中Optional的核心用法和最佳实践

《java中Optional的核心用法和最佳实践》Java8中Optional用于处理可能为null的值,减少空指针异常,:本文主要介绍java中Optional核心用法和最佳实践的相关资料,文中... 目录前言1. 创建 Optional 对象1.1 常规创建方式2. 访问 Optional 中的值2.1

git stash命令基本用法详解

《gitstash命令基本用法详解》gitstash是Git中一个非常有用的命令,它可以临时保存当前工作区的修改,让你可以切换到其他分支或者处理其他任务,而不需要提交这些还未完成的修改,这篇文章主要... 目录一、基本用法1. 保存当前修改(包括暂存区和工作区的内容)2. 查看保存了哪些 stash3. 恢

Python struct.unpack() 用法及常见错误详解

《Pythonstruct.unpack()用法及常见错误详解》struct.unpack()是Python中用于将二进制数据(字节序列)解析为Python数据类型的函数,通常与struct.pa... 目录一、函数语法二、格式字符串详解三、使用示例示例 1:解析整数和浮点数示例 2:解析字符串示例 3:解

C++/类与对象/默认成员函数@构造函数的用法

《C++/类与对象/默认成员函数@构造函数的用法》:本文主要介绍C++/类与对象/默认成员函数@构造函数的用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录名词概念默认成员函数构造函数概念函数特征显示构造函数隐式构造函数总结名词概念默认构造函数:不用传参就可以