SPFA 算法:实现原理及其应用

news/2024/10/20 11:41:41/

文章目录

    • 一、前言
    • 二、SPFA 算法
      • 1、SPFA算法的基本流程
      • 2、代码详解
    • 三、SPFA 算法已死 ?

一、前言

SPFA算法,全称为Shortest Path Faster Algorithm,是求解单源最短路径问题的一种常用算法,它可以处理有向图或者无向图,边权可以是正数、负数,但是不能有负环。

二、SPFA 算法

1、SPFA算法的基本流程

1. 初始化

首先我们需要起点s到其他顶点的距离初始化为一个很大的值(比如9999999,像是 JAVA 中可以设置 Integer.MAX_VALUE 来使),并将起点s的距离初始化为0。同时,我们还需要将起点s入队。

请添加图片描述

2. 迭代

每次从队列中取出一个顶点u,遍历所有从u出发的边,对于边(u,v)(其中v为从u可以到达的顶点),如果s->u->v的路径长度小于s->v的路径长度,那么我们就更新s->v的路径长度,并将v入队。

请添加图片描述

3. 循环

不断进行步骤2,直到队列为空。

4. 判断

最后,我们可以得到从起点s到各个顶点的最短路径长度,如果存在无穷小的距离,则说明从起点s无法到达该顶点。

其次,需要注意的是,SPFA算法中存在负环问题。如果存在负环,则算法会陷入死循环。因此,我们需要添加一个计数器,记录每个点进队列的次数。当一个点进队列的次数超过图中节点个数时,就可以判定存在负环。

2、代码详解

以下是使用Java实现 SPFA算法的代码,其中Graph类表示有向图或无向图,Vertex类表示图中的一个顶点,Edge类表示图中的一条边。

import java.util.*;class Graph {   // 图private List<Vertex> vertices;  // 顶点集public Graph() {vertices = new ArrayList<Vertex>();}public void addVertex(Vertex v) {   // 添加顶点vertices.add(v);}   // 添加顶点public List<Vertex> getVertices() { // 返回顶点return vertices;}   // 获取顶点集
}class Vertex {  // 点private int id; // 点 idprivate List<Edge> edges;   // 连接到该顶点的边private int distance;   // 从源顶点到该顶点的最短距离,MAX_VALUE initprivate boolean visited;    // 在图的遍历过程中是否访问过该顶点,false initpublic Vertex(int id) {this.id = id;edges = new ArrayList<Edge>();distance = Integer.MAX_VALUE;visited = false;}public int getId() {    // 获取 idreturn id;}public void addEdge(Edge e) {   // 将连接到该顶点边添加到列表中edges.add(e);}   // 添加图到边public List<Edge> getEdges() {  // 获取连接到该顶点的边集return edges;}   // 获取图中边public int getDistance() {  // 获取从源顶点到该顶点的最短距离return distance;}   // 获取源顶点到该顶点的最短距离public void setDistance(int distance) { //设置最短距离this.distance = distance;}   // 设置源顶点到该顶点的最短距离public boolean isVisited() {    // 获取在图的遍历过程中是否访问过该点return visited;}   // 获取图遍历过程中是否访问过该点public void setVisited(boolean visited) {   // 设置在图的遍历过程中是否访问过该点this.visited = visited;}   // 设置图遍历过程中是否访问过该点
}class Edge {    // 边private Vertex source;  // 源顶点private Vertex destination; // 目标顶点private int weight; // 边的权重public Edge(Vertex source, Vertex destination, int weight) {this.source = source;this.destination = destination;this.weight = weight;}public Vertex getSource() { // 返回源顶点return source;}   // 获取源点public Vertex getDestination() {    // 返回目标顶点return destination;}   // 获取目标顶点public int getWeight() {    // 获取边的权重return weight;}   // 获取边的权重
}// SPFA 算法
public class SPFA { public static void spfa(Graph graph, Vertex source) {// 初始化Queue<Vertex> queue = new LinkedList<Vertex>(); // 初始化一个顶点队列,使用该队列来跟中需要处理的顶点 for (Vertex v : graph.getVertices()) {  // 初始化最短距离和是否访问过该点v.setDistance(Integer.MAX_VALUE);v.setVisited(false);}source.setDistance(0); // 将源顶点到自身的最短距离设为0queue.add(source);  // 将源顶点添加到队列中// 迭代int count = 0;  // 用于检测图中的负环,count超过图中顶点的总数,抛出异常// 查找从一个源顶点到图中所有其它顶点的最短路径while (!queue.isEmpty()) {  Vertex u = queue.poll();    // 存储SPFA算法正在处理的顶点,poll 方法将下一个顶点从队列中取出u.setVisited(false);    // 标记该顶点为未访问,以便在算法中再次对其处理// 查找部分,循环遍历当前顶点 u 的所有边for (Edge e : u.getEdges()) {   Vertex v = e.getDestination();  // 返回边 e 的目标顶点给 vint distance = u.getDistance() + e.getWeight(); // 计算源顶点到目标顶点的距离if (distance < v.getDistance()) {v.setDistance(distance);    // 更新最短距离if (!v.isVisited()) {   // 如果该顶点未被访问过queue.add(v);   // 将该顶点添加到队列中v.setVisited(true); // 标记该顶点已被访问count++;    // 负环 + 1if (count > graph.getVertices().size()) {   // 检查 SPFA 算法处理的顶点数是否大于图中顶点总数throw new RuntimeException("Negative cycle detected");}}}}}}public static void main(String[] args) {// 构造图Graph graph = new Graph();// 构造顶点Vertex s = new Vertex(0);Vertex a = new Vertex(1);Vertex b = new Vertex(2);Vertex c = new Vertex(3);Vertex d = new Vertex(4);// 点加边s.addEdge(new Edge(s, a, 2));s.addEdge(new Edge(s, c, 1));a.addEdge(new Edge(a, b, 3));b.addEdge(new Edge(b, d, 2));c.addEdge(new Edge(c, d, 1));// 边加点graph.addVertex(s);graph.addVertex(a);graph.addVertex(b);graph.addVertex(c);graph.addVertex(d);// 调用SPFA算法求解最短路径spfa(graph, s);// 输出结果for (Vertex v :graph.getVertices()) {System.out.println("Shortest distance from source to vertex " + v.getId() + " is " + v.getDistance()); } } 
}

上面的代码实现了SPFA算法,并计算了从给定源点到图中其他所有顶点的最短路径。主要思路如下:

  1. 初始化:将所有顶点的距离设置为正无穷,将源点的距离设置为0,将源点加入队列。
  2. 迭代:从队列中取出一个顶点u,遍历它的所有邻居v。如果u到源点的距离加上u到v的边的权重小于v的距离,则更新v的距离,并将v加入队列中。如果v已经在队列中,则不需要再次添加。
  3. 如果队列为空,则算法结束。如果队列非空,则回到步骤2。

SPFA算法的时间复杂度取决于负权边的数量。如果图中没有负权边,算法的时间复杂度是O(E),其中E是边的数量。但是如果图中有负权边,算法的时间复杂度将达到O(VE),其中V是顶点的数量,E是边的数量。因此,为了避免算法的时间复杂度变得非常高,应尽可能避免在图中使用负权边。

三、SPFA 算法已死 ?

这个问题引发了很多OI选手和出题人的讨论,虽然 SPFA 算法在实际应用中具有一定的优势,但它也有一些缺点,导致它被称为"已死"的算法之一。以下是几个原因:

  • 可能会进入负环:SPFA 算法可以处理负权边,但是如果有负权环,算法将无法结束,因为每次都会沿着负权环一遍一遍地更新距离,导致算法陷入死循环。

  • 时间复杂度不稳定:在最坏情况下,SPFA 算法的时间复杂度可以达到 O ( V E ) O(VE) O(VE),其中 V V V E E E 分别是图中的顶点数和边数。而在最好情况下,时间复杂度只有 O ( E ) O(E) O(E)。因此,SPFA 算法的时间复杂度是不稳定的。

  • 存在更好的算法:对于单源最短路径问题,已经有更好的算法出现,如 Dijkstra 算法和 Bellman-Ford 算法。这些算法在时间复杂度和稳定性方面都比 SPFA 算法更优秀。

虽然 SPFA 算法在某些情况下可以发挥出优势,但是它的缺点也是无法忽视的,而且已经有更好的算法出现,不少大佬也或多或少的对 SPFA 算法进行了优化,更多优化的内容以及最短路径算法可以在论文中找到。因此,SPFA 算法已经不是首选算法,也可以说是已经“死亡”了。

在这里插入图片描述


http://www.ppmy.cn/news/53869.html

相关文章

Leetcode202. 快乐数

Every day a leetcode 题目来源&#xff1a;202. 快乐数 解法1&#xff1a;hash 根据几个例子&#xff0c;我们发现只有2种结果&#xff1a; 最终会得到1最终进入一个循环 其实得到1后&#xff0c;继续计算&#xff08;将该数替换为它每个位置上的数字的平方和&#xff09…

PyEcharts数据可视化(1)——配置项

PyEcharts 学习连接 一、查看pyecharts版本 import pyecharts print(pyecharts.__version__)输出&#xff1a;1.9.0 二、绘制第一个图表 from pyecharts.charts import Bar bar Bar() # 创建柱形图对象 bar.add_xaxis(["衬衫","羊毛衫","雪纺衫…

不得不用ChatGPT的100个理由……

❝ 最近无论在哪&#xff0c;很多人都在吹ChatGPT无所不能&#xff0c;动不动就是AI要颠覆人类&#xff0c;很多人害怕有一天AI会取代自己&#xff0c;我认为明显是多虑了…… ❝ 当然&#xff0c;也有很多小白试用了ChatGPT之后&#xff0c;并没有感觉到他很强大&#xff0c;主…

软件过程改进的12条

软件过程改进的12条&#xff1a; 过程改进必须做起来 软件工程是个实践的事&#xff0c;必须要做起来&#xff01; 我们实施软件过程改进&#xff0c;是为了提高组织的软件工程能力&#xff0c;而提高软件工程能力不是看你掌握了多少理论知识&#xff0c;能力必须通过实践获…

阿里测试8年,肝到P8只剩他了····

在阿里工作了8年&#xff0c;工作压力大&#xff0c;节奏快&#xff0c;但是从技术上确实得到了成长&#xff0c;尤其是当你维护与大促相关的系统的时候&#xff0c;熬到P7也费了不少心思&#xff0c;小编也是个爱学习的人&#xff0c;把这几年的工作经验整理成了一份完整的笔记…

Baklib如何帮助企业设计并维护FAQ页面?

作为现代企业的一部分&#xff0c;客户支持服务是为客户提供解决方案、回答问题和解决技术难题的关键部分。而其中最重要的一个基本工具是FAQ页面&#xff08;Frequently Asked Questions&#xff09;&#xff0c;它可以有效地减轻客户支持压力和提高工作效率。然而&#xff0c…

利用Iptables构建虚拟路由器

利用Iptables构建虚拟路由器 &#xff08;1&#xff09;修改网络类型 在VMware Workstation软件中选择“编辑→虚拟网络编辑器”菜单命令&#xff0c;在虚拟网络列表中选中VMnet1&#xff0c;将其配置为“仅主机模式&#xff08;在专用网络内连接虚拟机&#xff09;”&#x…

eBPF技术介绍

前言 eBPF起源于linux内核&#xff0c;它可以以砂箱程序运行在操作系统内核的特权上下文&#xff0c;高效&#xff0c;安全&#xff0c;易于扩展而不需要修改内核源码或者加载内核模块。 操作系统一直是实现观测&#xff0c;安全和网络功能的最理想的地方&#xff0c;因为内核的…