数据结构 —— BellmanFord算法

news2024/9/20 8:09:59

数据结构 —— BellmanFord算法

  • BellmanFord算法
  • 检测负权值环
  • BellmanFord和Dijkstra思想上的区别
      • Dijkstra算法的思想
      • Bellman-Ford算法的思想
      • 思想上的对比

我们今天来看一个算法BellmanFord算法,我们之前的Dijkstra算法只能用来解决正权图的单源最短路径问题。

Bellman-Ford算法是一种用于计算单源最短路径问题的算法,也就是说,它能找出一个图中某个特定顶点到所有其他顶点的最短路径。与Dijkstra算法不同,Bellman-Ford算法可以处理含有负权边的图,但不能处理包含负权环的图(因为从源点到包含负权环的任意点的距离可以无限减小)。

以下是Bellman-Ford算法的基本步骤:

  1. 初始化:将源点到自身的距离设为0,源点到其他所有顶点的距离设为无穷大。
  2. 放松操作:对图中的每条边进行V-1次放松操作,其中V是顶点的数量。在每次放松操作中,对于每条边(u, v),如果dist[v] > dist[u] + weight(u, v),则更新dist[v] = dist[u] + weight(u, v)。其中,dist[v]表示源点到v的当前最短路径长度,weight(u, v)表示边(u, v)的权重。
  3. 检测负权环:再进行一次边的放松操作。如果此时仍存在某条边(u, v)满足dist[v] > dist[u] + weight(u, v),则说明图中存在负权环。

我们先构建一个这样的图:
在这里插入图片描述在这里插入图片描述

BellmanFord算法

BellmanFord算法是站在全局的角度来思考问题,如果这条边可以通过另一条边得到一个更小的结果,就更新,基于这样的思想,我们可以暴力循环来解决:

		bool BellmanFord(const V& srci, vector<W>& dest, vector<int>& parentPath)
		{
			//结点转化
			size_t srcIndex = FindSrci(srci);

			parentPath.resize(_vertex.size(), -1);

			dest.resize(_vertex.size(), MAX_W);

			dest[srcIndex] = W();

			for (size_t i = 0; i < _vertex.size(); i++)
			{
				for (size_t j = 0; j < _vertex.size(); j++)
				{
					if (_matrix[i][j] != MAX_W &&
						dest[j] > _matrix[i][j] + dest[i])
					{
						dest[j] = _matrix[i][j] + dest[i];
						parentPath[j] = i;
					}
				}
			}

			return true;
		}
	void TestGraphBellmanFord()
	{
		const char* str = "syztx";
		Graph<char, int, INT_MAX, true> g(str, strlen(str));
		g.AddEdge('s', 't', 6);
		g.AddEdge('s', 'y', 7);
		g.AddEdge('y', 'z', 9);
		g.AddEdge('y', 'x', -3);
		g.AddEdge('z', 's', 2);
		g.AddEdge('z', 'x', 7);
		g.AddEdge('t', 'x', 5);
		g.AddEdge('t', 'y', 8);
		g.AddEdge('t', 'z', -4);
		g.AddEdge('x', 't', -2);
		g.Print();


		vector<int> dist;
		vector<int> parentPath;

		g.BellmanFord('s', dist, parentPath);
		g.PrintShortestPath('s', dist, parentPath);
	}

在这里插入图片描述

我们发现路径是对的,但是权值不对
在这里插入图片描述
这是为什么呢?我们把选边过程挑出来:

bool BellmanFord(const V& srci, vector<W>& dest, vector<int>& parentPath)
		{
			//结点转化
			size_t srcIndex = FindSrci(srci);

			parentPath.resize(_vertex.size(), -1);

			dest.resize(_vertex.size(), MAX_W);

			dest[srcIndex] = W();

			cout << "开始选边: " << endl;
			for (size_t i = 0; i < _vertex.size(); i++)
			{
				for (size_t j = 0; j < _vertex.size(); j++)
				{
					
					if (_matrix[i][j] != MAX_W &&
						dest[j] > _matrix[i][j] + dest[i])
					{
						cout << _vertex[i] << "->" << _vertex[j] << ":" << _matrix[i][j]
							<< endl;
						dest[j] = _matrix[i][j] + dest[i];
						parentPath[j] = i;
					}
				}
			}

			return true;
		}

在这里插入图片描述

问题就出在这两个地方:
在这里插入图片描述

在这里插入图片描述
我们之后的结果会对之前的结果有影响,所以我们还有套一层循环来保证我们的每条边都进行了更新:

			for (size_t k = 0; k < _vertex.size(); k++)
			{
				for (size_t i = 0; i < _vertex.size(); i++)
				{
					for (size_t j = 0; j < _vertex.size(); j++)
					{

						if (_matrix[i][j] != MAX_W &&
							dest[j] > _matrix[i][j] + dest[i])
						{
							cout << _vertex[i] << "->" << _vertex[j] << ":" << _matrix[i][j]
								<< endl;
							dest[j] = _matrix[i][j] + dest[i];
							parentPath[j] = i;
						}
					}
				}
			}
			cout << endl;

			return true;
		}

在这里插入图片描述
这下权值就是对的,但是在更新过程中有些边是不用更新的,所以我们可以设计一个标志位来提高效率:

		bool BellmanFord(const V& srci, vector<W>& dest, vector<int>& parentPath)
		{
			//结点转化
			size_t srcIndex = FindSrci(srci);

			parentPath.resize(_vertex.size(), -1);

			dest.resize(_vertex.size(), MAX_W);

			dest[srcIndex] = W();

			for (size_t k = 0; k < _vertex.size(); k++)
			{
				bool exchange = false;
				cout << "开始选边: " << endl;
				for (size_t i = 0; i < _vertex.size(); i++)
				{
					for (size_t j = 0; j < _vertex.size(); j++)
					{
						if (_matrix[i][j] != MAX_W &&
							dest[j] > _matrix[i][j] + dest[i])
						{
							cout << _vertex[i] << "->" << _vertex[j] << ":" << _matrix[i][j]
								<< endl;
							dest[j] = _matrix[i][j] + dest[i];
							parentPath[j] = i;

							exchange = true;
						}
					}
				}

				if (exchange == false)
				{
					break;
				}


			}
			cout << endl;

			return true;
		}

在这里插入图片描述

检测负权值环

如果这个图中有负权值环就会导致距离可以无限减小
在这里插入图片描述
所以我们的还有能力检测负权值环:

		bool BellmanFord(const V& srci, vector<W>& dest, vector<int>& parentPath)
		{
			//结点转化
			size_t srcIndex = FindSrci(srci);

			parentPath.resize(_vertex.size(), -1);

			dest.resize(_vertex.size(), MAX_W);

			dest[srcIndex] = W();

			for (size_t k = 0; k < _vertex.size(); k++)
			{
				bool exchange = false;
				cout << "开始选边: " << endl;
				for (size_t i = 0; i < _vertex.size(); i++)
				{
					for (size_t j = 0; j < _vertex.size(); j++)
					{
						if (_matrix[i][j] != MAX_W &&
							dest[j] > _matrix[i][j] + dest[i])
						{
							cout << _vertex[i] << "->" << _vertex[j] << ":" << _matrix[i][j]
								<< endl;
							dest[j] = _matrix[i][j] + dest[i];
							parentPath[j] = i;

							exchange = true;
						}
					}
				}

				if (exchange == false)
				{
					break;
				}


			}

			for (size_t i = 0; i < _vertex.size(); ++i)
			{
				for (size_t j = 0; j < _vertex.size(); ++j)
				{
					// 检查有没有负权回路
					if (_matrix[i][j] != MAX_W
						&& dest[i] + _matrix[i][j] < dest[j])
					{
						return false;
					}
				}
			}

			return true;
		}

我们这里举个例子:

	void TestGraphBellmanFord()
	{
		const char* str = "syztx";
		Graph<char, int, INT_MAX, true> g(str, strlen(str));
		g.AddEdge('s', 't', 6);
		g.AddEdge('s', 'y', 7);
		g.AddEdge('y', 'z', 9);
		g.AddEdge('y', 'x', -3);
		g.AddEdge('z', 's', 2);
		g.AddEdge('z', 'x', 7);
		g.AddEdge('t', 'x', 5);
		g.AddEdge('t', 'y', -8); //修改
		g.AddEdge('t', 'z', -4);
		g.AddEdge('x', 't', -2);
		g.AddEdge('y', 's', 1); // 新增
		g.Print();


		vector<int> dist;
		vector<int> parentPath;

		if(g.BellmanFord('s', dist, parentPath))
			g.PrintShortestPath('s', dist, parentPath);
		else
			cout << "存在负权回路" << endl;
	}

在这里插入图片描述

BellmanFord和Dijkstra思想上的区别

Bellman-Ford算法和Dijkstra算法在思想上的主要区别在于它们处理最短路径问题的方式以及它们对图中边权重的假设。下面详细解释这两种算法在思想上的差异:

Dijkstra算法的思想

Dijkstra算法基于贪心策略,它维护一个顶点集合S,其中包含了已经确定了从源点到这些顶点的最短路径的所有顶点。算法的核心思想是每次从未确定最短路径的顶点中选取距离源点最近的那个顶点加入集合S,并更新与之相邻的顶点的距离。

  1. 初始化:从源点开始,将其距离设为0,其他所有顶点的距离设为无穷大。
  2. 迭代过程:每次迭代选择未被访问过的、距离源点最近的顶点u,将u标记为已访问(加入S集合),并尝试通过u更新其所有未访问邻居的距离。如果通过u到达邻居v的总距离小于v当前记录的距离,则更新v的距离。
  3. 终止条件:当所有顶点都被访问过,或者当前最小距离的顶点距离为无穷大时,算法结束。

Bellman-Ford算法的思想

Bellman-Ford算法则采用了动态规划的思想,它通过逐步松弛所有的边,来逼近最短路径的正确解。算法的核心是重复执行“松弛”操作,直到不再有路径可以改进为止。

  1. 初始化:同样地,从源点开始,将其距离设为0,其他所有顶点的距离设为无穷大。
  2. 松弛操作:算法会遍历图中的所有边多次,每次遍历都尝试通过边的两端点更新路径距离。如果通过某条边可以得到更短的路径,就更新这条路径的距离。这个过程会重复V-1次(V为顶点数量),因为在任何无环图中,从源点到任意顶点的最短路径至多包含V-1条边。
  3. 负权重循环检测:在进行了V-1轮的松弛操作后,如果再次遍历所有边时还能进一步更新某个顶点的距离,那就意味着图中存在负权重循环。

思想上的对比

  • 适应性:Dijkstra算法假设所有边的权重都是非负的,而Bellman-Ford算法可以处理负权重边(只要不存在负权重循环)。
  • 效率:Dijkstra算法在处理无负权重边的图时通常比Bellman-Ford算法更高效,尤其是在使用优先队列优化的情况下。
  • 动态规划vs贪心策略:Bellman-Ford算法通过重复松弛所有边来逐渐逼近最短路径,体现了动态规划的思想;而Dijkstra算法通过每次选择局部最优解来逐步构建全局最优解,体现了贪心策略。

总体来说,Dijkstra算法和Bellman-Ford算法各自适用于不同的场景,选择哪个算法取决于图的特性和你对时间和空间效率的需求。

附上源码:

bool BellmanFord(const V& srci, vector<W>& dest, vector<int>& parentPath)
{
    // 将源点名称转换为其在顶点列表中的索引
    size_t srcIndex = FindSrci(srci);

    // 初始化parentPath向量,用于存储最短路径上的前驱顶点
    parentPath.resize(_vertex.size(), -1);

    // 初始化dest向量,用于存储源点到各顶点的最短距离
    dest.resize(_vertex.size(), MAX_W); // MAX_W代表无穷大

    // 设置源点到自身的距离为0
    dest[srcIndex] = W(); // W()应为权重类型的默认构造函数,通常为0

    // 开始Bellman-Ford算法的V-1轮松弛操作
    for (size_t k = 0; k < _vertex.size(); k++)
    {
        bool exchange = false; // 用于检测本轮是否有路径更新
        cout << "开始选边: " << endl;
        // 遍历图中所有的边
        for (size_t i = 0; i < _vertex.size(); i++)
        {
            for (size_t j = 0; j < _vertex.size(); j++)
            {
                // 如果边(i, j)存在且通过边(i, j)可以得到更短的路径
                if (_matrix[i][j] != MAX_W && 
                    dest[j] > _matrix[i][j] + dest[i])
                {
                    cout << _vertex[i] << "->" << _vertex[j] << ":" << _matrix[i][j]
                         << endl; // 输出更新的边信息
                    dest[j] = _matrix[i][j] + dest[i]; // 更新dest[j]的值
                    parentPath[j] = i; // 更新parentPath[j],记录前驱顶点

                    exchange = true; // 标记发生了路径更新
                }
            }
        }

        // 如果一轮迭代中没有发生路径更新,则提前退出循环
        if (exchange == false)
        {
            break;
        }
    }

    // 检查是否存在负权重循环
    for (size_t i = 0; i < _vertex.size(); ++i)
    {
        for (size_t j = 0; j < _vertex.size(); ++j)
        {
            // 如果通过边(i, j)可以进一步缩短路径,说明存在负权重循环
            if (_matrix[i][j] != MAX_W &&
                dest[i] + _matrix[i][j] < dest[j])
            {
                return false;
            }
        }
    }

    // 如果没有发现负权重循环,返回true,表示算法成功
    return true;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1916761.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机的错误计算(二十七)

摘要 介绍错数&#xff1a;任给一个单变元函数&#xff0c;当自变量被截断时&#xff0c;函数值中含有的错误的有效数字个数&#xff0c;并给出其计算方法。 首先&#xff0c;从字面上看&#xff0c;错数表示错误的有效数字个数。 下面从一个略显粗糙的化简过程&#xff0c;推…

数据结构-散列表(hash table)

6.1 散列表的概念 散列表又叫哈希&#xff08;hash&#xff09;表&#xff0c;是根据键&#xff08;key&#xff09;直接访问在内存存储位置的值&#xff08;value&#xff09;的数据结构&#xff0c;由数组演化而来&#xff08;根据数组支持按照下标进行随机访问数据的特性&a…

14-60 剑和诗人34 - Kubernetes 是部署 LLM 的首选平台

​​​​ 介绍 近年来&#xff0c;大型语言模型 (LLM) 一直在彻底改变自然语言处理领域。从 GPT-3 到 PaLM 等&#xff0c;这些模型可以生成类似人类的文本、回答问题、总结文档等等。然而&#xff0c;训练和部署 LLM 需要大量的计算。随着这些模型的规模和能力不断增长&#…

类和对象——【const成员】【static成员】【友元】【内部类】

P. S.&#xff1a;以下代码均在VS2019环境下测试&#xff0c;不代表所有编译器均可通过。 P. S.&#xff1a;测试代码均未展示头文件iostream的声明&#xff0c;使用时请自行添加。 博主主页&#xff1a;Yan. yan.                        …

有必要找第三方软件测评公司吗?如何选择靠谱软件测评机构?

软件测试是确保软件质量的重要环节&#xff0c;而在进行软件测试时&#xff0c;是否有必要找第三方软件测评公司呢?第三方软件测评公司是指独立于软件开发公司和用户之间的中立机构&#xff0c;专门从事软件测试和测评工作。与自身开发团队或内部测试团队相比&#xff0c;选择…

修BUG:程序包javax.servlet.http不存在

貌似昨晚上并没有成功在tomcat上面运行&#xff0c;而是直接运行了网页。 不知道为啥又报错这个。。。 解决方案&#xff1a; https://developer.baidu.com/article/details/2768022 就整了这一步就行了 而且我本地就有这个tomcat就是加进去了。 所以说啊&#xff0c;是不是&a…

CentOS7 安装 git 命令

通过yum源install下载的git版本比较低&#xff0c;不推荐此方式安装。 官网下载最新版git源码&#xff1a;Git 1. 解压安装包 tar -xzvf git-2.45.2.tar.gz 2. 安装相关依赖 yum install curl-devel expat-devel gettext-devel openssl-devel zlib-devel gcc perl-ExtUtils…

政安晨:【Keras机器学习示例演绎】(五十四)—— 使用神经决策森林进行分类

目录 导言 数据集 设置 准备数据 定义数据集元数据 为训练和验证创建 tf_data.Dataset 对象 创建模型输入 输入特征编码 深度神经决策树 深度神经决策森林 实验 1&#xff1a;训练决策树模型 实验 2&#xff1a;训练森林模型 政安晨的个人主页&#xff1a;政安晨 欢…

Git常见命令和用法

Git 文件状态 Git 文件 2 种状态: 未跟踪:新文件&#xff0c;从未被 Git 管理过已跟踪:Git 已经知道和管理的文件 常用命令 命令作用注意git -v查看 git 版本git init初始化 git 仓库初始化之后有工作区、暂存区(本地库)、版本库git add 文件标识暂存某个文件文件标识以终…

吹田电气绿色能源 未来可期

在2024年7月的上海慕尼黑电子展上&#xff0c;吹田电气功率分析仪成为了备受瞩目的明星产品。作为电子测试与测量领域的重要工具&#xff0c;功率分析仪在展会上展示了其在绿色能源和高效能量管理方面的最新应用&#xff0c;引发了广泛关注和热议。 领先技术&#xff0c;精准测…

科普文:jvm笔记

一、JVM概述# 1. JVM内部结构# 跨语言的平台&#xff0c;只要遵循编译出来的字节码的规范&#xff0c;都可以由JVM运行 虚拟机 系统虚拟机 VMvare 程序虚拟机 JVM JVM结构 HotSpot虚拟机 详细结构图 前端编译器是编译为字节码文件 执行引擎中的JIT Compiler编译器是把字节…

untiy 在菜单栏添加自定义按钮 点击按钮弹出一个Unity窗口,并在窗口里添加属性

using System.Collections.Generic; using UnityEditor; using UnityEngine; using UnityEngine.Rendering.PostProcessing;public class AutoGenerateWindow : EditorWindow //这是定义一个窗口 {public string subjecttName "科目名字";//科目的名字public GameOb…

Python | Leetcode Python题解之第228题汇总区间

题目&#xff1a; 题解&#xff1a; class Solution:def summaryRanges(self, nums: List[int]) -> List[str]:def f(i: int, j: int) -> str:return str(nums[i]) if i j else f{nums[i]}->{nums[j]}i 0n len(nums)ans []while i < n:j iwhile j 1 < n …

android13 rom 开发总纲说明

1. 这里是文章总纲&#xff0c;可以在这里快速找到需要的文章。 2. 文章一般是基于标准的android13&#xff0c;有一些文章可能会涉及到具体平台&#xff0c;例如全志&#xff0c;瑞芯微等一些平台。 3.系统应用 3.1系统应用Launcher3桌面相关&#xff1a; 3.2系统应用设置S…

获奖案例回顾|基于卫星遥感和无人机的水稻全流程风险减量项目

引言 在现代农业保险领域&#xff0c;技术创新是推动行业进步的关键。珈和科技与太平财险的合作&#xff0c;旨在利用先进的卫星遥感和无人机技术&#xff0c;解决传统农业保险面临的诸多挑战&#xff0c;从而提升保险效率和服务质量。本次分享的项目案例获得了《金融电子化》…

关于无法定位程序输入点 SetDefaultDllDirectories于动态链接库KERNEL32.dll 上 解决方法

文章目录 1. ERNEL32.dll 下载2. 解决方法 &#x1f44d; 个人网站:【 洛秋小站】 1. ERNEL32.dll 下载 Windows 7 在安装postman时报错缺少动态链接库,提示缺少.NET Framework,这是因为本地缺少相应的dll文件导致的&#xff0c;这时就需要下载ERNEL32.dll文件&#xff0c;在解…

亚马逊云科技EC2简明教程

&#x1f4a1; 完全适用于新手操作的Amazon EC2引导教程 简述 在亚马逊云科技中&#xff0c;存在多种计算服务&#xff0c;在此&#xff0c;我们将会着重讨论Amazon EC2(以下简称EC2)&#xff0c;EC2作为亚马逊云科技的明星产品、核心产品&#xff0c;是大多数开发者和企业用…

13个Python自动化实战脚本

1、批量文件重命名神器在工作中&#xff0c;我们常常需要对大量文件进行批量重命名&#xff0c;Python帮你轻松搞定&#xff01; 2、自动发送邮件通知告别手动发送&#xff0c;用Python编写定时发送邮件的自动化脚本。 3、定时任务自动化执行使用Python调度库&#xff0c;实现定…

【Adobe】动作捕获和动画制作软件Character Animator

Adobe Character Animator 是一款由Adobe公司出品的动作捕获和动画制作软件&#xff0c;旨在帮助用户直观地制作2D&#xff08;二维&#xff09;人物动画、实时动画&#xff0c;并发布动画。这款软件功能强大、操作简单&#xff0c;非常适合动画制作者、直播主以及社交媒体内容…

如何将Docker镜像源更改为阿里云的镜像加速地址

在使用Docker时&#xff0c;尤其是在国内环境下&#xff0c;由于网络原因&#xff0c;从Docker Hub拉取镜像可能会遇到速度较慢的问题。为了提高拉取速度&#xff0c;我们可以将Docker的镜像源更改为阿里云等国内镜像源。下面详细介绍如何获取并配置阿里云的Docker镜像加速地址…