最短路径(Dijkstra算法与Floyd算法)

news2024/9/22 5:30:51

一、Dijkstra算法

Dijkstra算法与之前学习过的Prim算法有些相似之处。我们直接通过一个例子来讲解

假设要求的是A->E之间的最短路径。首先我们来列出顶点A到其他各顶点的路径长度:A->D = 2,A->B = 6,A->C = 1,A->E = ∞。既然是要寻找最短路径,我们当然是先在已有的路径里面挑一条最短的,也就是A->C。将到达过的顶点用红色进行标识

到达C点后,我们又可以找到两条路径:C->B = 5,C->E = 7。此时我们拿这几条新的路径长度,与之前的A->C = 1相加,就可以得到A->B = 6,A->E = 8。出现了一条比之前短的路径:A->E = 8。所以我们将其更新到之前的路径列表里:A->D = 2,A->B = 6,A->C = 1,A->E = 8

接下来再从路径列表里选择一条最短的路径,也就是:A->D = 2。将D标记为已到达。

到达D点后,又可以找到两条新的路径:D->B = 3,D->E = 6。与A->D = 2相加可得:A->B = 5,A-E = 8。又有一条比之前短的路径:A->B = 5,所以将其更新到路径列表:A->D = 2,A->B = 5,A->C = 1,A->E = 8

再选择一条最短的路径:A->B = 5。将B标记为已到达。

这次只有一条新的路径:B->E = 2。将其与A->B = 5相加得:A->E = 7。比之前的路径更短,所以更新到路径列表:A->D = 2,A->B = 5,A->C = 1,A->E = 7。最后来到E点,结束遍历。

与此同时,我们获得了一张从A到各个顶点的最短路径长度表。

代码如下

public struct Path
{
	// 上一顶点下标
	public int preNode;
	// 起始顶点到当前顶点的总路径长度
	public int totalWeight;

	public Path(int preNode, int totalWeight)
	{
		this.preNode = preNode;
		this.totalWeight = totalWeight;
	}
}

/// <summary>  
/// Dijkstra算法  
/// </summary>  
/// <param name="graph"></param>  
/// <param name="startIndex"></param>
public Path[] Dijkstra<T>(GraphByAdjacencyMatrix<T> graph,int startIndex)
{
	// 用来存储顶点是否访问过
	bool[] flags = new bool[graph.Count];
	// 存储起始顶点到其他各顶点的最短路径长度
	Path[] paths = new Path[graph.Count];

	// 初始化起始顶点到其他顶点的路径
	for (int i = 0; i < graph.Count; i++)
	{
		paths[i] = new Path(startIndex, graph.Matrix[startIndex, i]);
	}
	// 将初始顶点设为已访问
	flags[startIndex] = true;
	int minIndex = 0;
	
	for (int i = 1; i < graph.Count; i++)
	{
		int min = Int32.MaxValue;
		// 寻找已存储路径中的最短路径
		for (int j = 0; j < graph.Count; j++)
		{
			if (!flags[j] && paths[j].totalWeight < min)
			{
				min = paths[j].totalWeight;
				minIndex = j;
			}
		}
		
		// 将最近的顶点设为已访问
		flags[minIndex] = true;
		// 基于当前顶点,查找更远顶点的最短路径
		for (int j = 0; j < graph.Count; j++)
		{
			// 把min放在右侧防止溢出
			if (!flags[j] && graph.Matrix[minIndex, j] < paths[j].totalWeight - min)
			{
				paths[j].preNode = minIndex;
				paths[j].totalWeight = min + graph.Matrix[minIndex, j];
			}
		}
	}

	return paths;
}

/// <summary>
/// 输出最短路径
/// </summary>
/// <param name="graph"></param>
/// <param name="startIndex"></param>
/// <param name="endIndex"></param>
private void PrintShortestPathByDijkstra<T>(GraphByAdjacencyMatrix<T> graph,int startIndex,int endIndex)
{
	var paths = Dijkstra<T>(graph, startIndex);
	var stack = new Stack<T>(graph.Count);
	int curIndex = endIndex;
	
	while (curIndex != startIndex)
	{
		stack.Push(graph.Nodes[curIndex]);
		curIndex = paths[curIndex].preNode;
	}
	stack.Push(graph.Nodes[startIndex]);
	while (stack.Count > 0)
	{
		Console.Write(stack.Pop());
		if(stack.Count != 0)
			Console.Write("->");
	}
	
	Console.Write(" 路径长度="+paths[endIndex].totalWeight);
}

代码部分也与Prim算法的代码高度相似。引用一位知乎大佬的话来说,Prim算法更新的是未标记集合到已标记集合之间的距离;而Dijkstra算法更新的是原点到未标记集合之间的距离。

二、Floyd算法

与Dijkstra算法相比,Floyd算法就显得比较“暴力”了。我们来看下面这张图

假设我们要求从A到B的最短路径。首先最直观的路径就是A直接到B:A->B = 9。但假如我们找一个中间节点,也就是C,就会发现A->C->B = 7要比A->B = 9更短。因此现在A到B的最短路径更新为:A->C->B = 7

接下来看C点。C到B的直连路径为:C->B = 3。但是如果找中间节点D,就会发现C->D->B = 2C->B = 3更短。因此C到B的最短路径更新为:C->D->B = 2

最终,A到B的最短路径就是:A->C->D->B = 6

这就是Floyd算法的核心思想。

下面来通过一个具体的例子讲解算法流程。首先需要准备两个二维数组,weights数组用来存储各顶点之间的路径长度,paths数组用来存储路径的中间节点。在初始状态下,weights数组初始化为与图的邻接矩阵一致,paths数组初始化为路径的尾结点(如A->B的中间节点设置为B)。

首先,以A节点为中间节点,计算其他两个顶点经过A点相连的路径和,并与直连的路径相比较。如果经过A节点的方案更短,则更新到这两个矩阵中。比如B->C = 5,如果经过A节点则B->A->C = 7,路径变长了,所以不需要更新。又如C->D = ∞,如果经过A节点,则C->A->D = 3,路径变短了,所以需要更新。

接下来再以B为中间节点进行计算。

以此类推,直到整个矩阵全部更新完。最终的结果如下

现在假如我们要找A到E的最短路径。从weights数组可得A到E的最短路径长度为7。然后根据paths求得具体路径。首先根据paths[0][4] = 3可知,从A到E需要经过D点,也就是A->D。然后再根据paths[3][4] = 1可知,从D到E需要先经过B点,即A->D->B。再根据paths[1][4] = 4可知,从B到E可以直达,即A->D->B->E

代码如下

/// <summary>
/// Floyd算法
/// </summary>
/// <param name="graph"></param>
public Path[,] Floyd<T>(GraphByAdjacencyMatrix<T> graph)
{
	// 这里可以复用前面的Path结构体
	Path[,] paths = new Path[graph.Count,graph.Count];
	
	// 将路径长度初始化为与邻接矩阵一致
	// 将路径中间点初始化为边的尾结点
	for (int i = 0; i < graph.Count; i++)
	{
		for (int j = 0; j < graph.Count; j++)
		{
			paths[i, j].totalWeight = graph.Matrix[i, j];
			paths[i, j].preNode = j;
		}
	}

	// 依次寻找经过中间节点小于直达的路径
	for (int nodeIndex = 0; nodeIndex < graph.Count; nodeIndex++)
	{
		for (int row = 0; row < graph.Count; row++)
		{
			for (int col = 0; col < graph.Count; col++)
			{
				// 求和转换为求差防止溢出
				if (paths[row, col].totalWeight - paths[nodeIndex,col].totalWeight > paths[row,nodeIndex].totalWeight )
				{
					paths[row, col].totalWeight =
						paths[row, nodeIndex].totalWeight + paths[nodeIndex, col].totalWeight;
					paths[row, col].preNode = nodeIndex;
				}
			}
		}
	}
	return paths;
}

/// <summary>
/// 输出最短路径
/// </summary>
/// <param name="graph"></param>
/// <param name="startIndex"></param>
/// <param name="endIndex"></param>
private void PrintShortestPathByFloyd<T>(GraphByAdjacencyMatrix<T> graph,int startIndex,int endIndex)
{
	var paths = Floyd<T>(graph);
	int curIndex = startIndex;
	
	while (curIndex != endIndex)
	{
		Console.Write(graph.Nodes[curIndex]+"->");
		curIndex = paths[curIndex, endIndex].preNode;
	}
	Console.Write(graph.Nodes[endIndex]);
	Console.Write(" 路径长度="+paths[startIndex,endIndex].totalWeight);
}

三、参考资料

[1].《大话数据结构》
[2]. https://zhuanlan.zhihu.com/p/87748517
[3]. https://zhuanlan.zhihu.com/p/72248451

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

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

相关文章

MySQL主从复制

MySQL主从复制 MySQL主从复制原理 主服务器在更新数据前&#xff0c;会写入硬盘&#xff0c;银盘在再将数据写入二进制日志 从服务器开启I/O线程&#xff0c;Master节点为每个I/O线程启动一个dump线程用于发送二进制事件到从服务器的中继日志中 从服务器的sql线程开启&…

springboot集成dubbo配置多注册中心

1 dubbo多注册中心 dubbo可以支持多注册中心&#xff0c;以及多协议, 本文示例dubbo同时注册到nacos和zookeeper注册中心&#xff1a; 在前文基础上&#xff0c;给provider consumer模块加上zookeeper依赖&#xff1a; <dependency><groupId>org.apache.dubbo<…

TypeScript26(TS进阶用法Record Readonly)

Readonly Readonly与我们上一章节学的Partial 很相似&#xff0c;只是把? 替换成了 Readonly // 源码 type Readonly<T> {readonly [P in keyof T]: T[P]; }; 疑问&#xff1a; keyof 是干什么的&#xff1f; in 是干什么的&#xff1f; Readonly 是将该属性变为…

【HBU】数据结构第一次月测题(线性结构)

数据结构第一次月测题 判断题&#xff1a; 1.在具有N个结点的单链表中&#xff0c;访问结点和增加结点的时间复杂度分别对应为O&#xff08;1&#xff09;和O&#xff08;N&#xff09; F 访问节点的时间复杂度为O(N) 2.对于顺序存储长度为N的线性表&#xff0c;…

DataBinding原理----双向绑定(4)

前面的几种文章分析了DataBinding单向数据绑定的原理&#xff0c;今天来看看双向数据绑定是怎么回事。 我们知道单向绑定是在数据发生变化的时候能够通知到UI&#xff0c;让数据的变化能够及时反应到UI上&#xff1b;而双向绑定则是不仅要让数据的变化能够反馈到UI上&#xff0…

web前端-javascript-立即执行函数(说明、例子)

立即执行函数 /* (function(){alert("我是一个匿名函数~~~"); })(); */(function (a, b) {console.log("a " a);console.log("b " b); })(123, 456);1. 说明 函数定义完&#xff0c;立即被调用&#xff0c;这种函数叫做立即执行函数立即执…

Twitter群推解锁流量大门的钥匙

Twitter作为全球最知名的社交媒体平台之一&#xff0c;对海外营销有着巨大的影响力&#xff0c;是外贸企业进行群推、群发、引流必不可少的平台。那么要想通过推特群推、推特群发打开流量的大门&#xff0c;这里有几点值得大家注意&#xff0c;帮助你更好的驾驭流量&#xff1a…

虚拟机安装zookeeper集群

一、准备 克隆原先的虚拟机;因为是从原先已有jdk和zk的linux虚拟机克隆过来的,所以克隆的虚拟机上是一样的! 三台虚拟机,我采用的是:zk的ip不一样,端口一样 修改每台虚拟机上环境变量,zk配置文件 修改zookeeper配置文件,采用默认端口,配置主从节点

Bootstrap主页面搭建(十四)

创建主页面&#xff1a;index.jsp&#xff1a; 引入bootstrap依赖&#xff1a; 首先写导航条&#xff0c;复制代码更改&#xff1a; <!--导航条--> <nav class"navbar navbar-inverse"><div class"container-fluid"><!-- Brand and…

Nginx配置实例-动静分离

1、什么是动静分离 Nginx动静分离简单来说就是把动态跟静态请求分开&#xff0c;不能理解成只是单纯的把动态页面和 静态页面物理分离。严格意义上说应该是动态请求跟静态请求分开&#xff0c;可以理解成使用Nginx 处理静态页面&#xff0c;Tomcat处理动态页面。 动静分离从目…

Project joee 算法开发日志(一)

目录一. 下载并安装TensorRT1.1 下载安装TensorRT1.2 验证TensorRT安装是否成功二. 安装并测试Windows预测库2.1 安装cuda11.0_cudnn8.0_avx_mkl-trt7.2.1.6 预测库2.2 测试精度损失2.3 推理速度测试三. 总结开发机器配置&#xff1a;CPU: AMD5800 8core 16ThreadGPU: NVIDIA G…

微信支付回调,内网穿透详细过程

文章目录支付回调接口通过Ngrok进行内网穿透步骤1. 根据邮箱注册一个账号2. 获取隧道id3.下载Ngrok客户端4. 双击这个 Sunny-Ngrok启动工具.bat 文件5. 填写你的 隧道id 回车6.客户端启动成功7. 所以你的notify_url对应的value需要改为内网穿透的地址为8.支付成功之后微信平台会…

分面中添加直线

简介 这篇也是分享最近统计建模中所绘制的一副图形。总体而言和前面的几篇&#xff1a;xxx 类似。都是从“数据导入”到“基于分面的可视化”。但是本文的小技巧是&#xff0c;在不同的分面中添加直线。最后得到的图形如下&#xff1a; 注意&#xff1a;本文数据和代码在公众号…

交易所通用质押式回购

一、专业术语 逆回购&#xff1a;指资金融出方将资金融给资金融入方&#xff0c;收取有价证券作为质押&#xff0c;并在未来收回本息&#xff0c;并解除有价证券质押的交易行为。 债券通用质押式回购交易&#xff1a;&#xff08;简称“通用回购”&#xff09;是指资金融入方…

划分成绩ABCD

已知成绩等级划分为{“A”:[90~100],"B":[80~89],"c":[60~79],"D":[0~59]} 1、随机生成20个整数&#xff0c;范围0-100 2、按等级归类&#xff0c;输出成绩等级列表字典如下&#xff1a; {A: [96, 96, 97, 97, 100, 100], B: [86], C: [71, 7…

Python学习基础笔记二十二——生成器

一个包含yield关键字的函数就是一个生成器函数。yield可以为我们从函数中返回值&#xff0c;但是yield又不同于return&#xff0c;return的执行意味着程序的结束&#xff0c;调用生成器函数不会得到返回的具体的值&#xff0c;而是得到一个可迭代的对象。每一次获取这个可迭代对…

微机原理与接口技术:数模转换和模数转换 详细笔记

文章目录1.数模转换1.1.数模转换原理1.1.1.权电阻D/A转换器1.1.2.R-2R T型电阻网络D/A转换器1.1.3.补充 D/A转换器的主要技术指标1.2.D/A转换芯片——DAC08321.2.1.引脚介绍1.2.2.工作方式直通输入方式单缓冲方式双缓冲方式2.模数转换2.1.信号变换中的采样、量化和编码2.1.1.采…

『NLP学习笔记』TextCNN文本分类原理及Pytorch实现

TextCNN文本分类原理及Pytorch实现 文章目录一. TextCNN网络结构1.1. CNN在文本分类上得应用1.2. 回顾CNN以及Pytorch解析1.2.1. CNN特点1.2.2. 一维卷积Conv1d1.2.3. 二维卷积 Conv2d1.2.3. 三维卷积 Conv3d1.2.4. 池化(pooling)操作1.2.4. nn.BatchNorm操作1.3. nn.ModuleLi…

大数据之数据的压缩与存储

文章目录前言一、Hive的压缩方式&#xff08;一&#xff09; 概念&#xff08;二&#xff09; 简介&#xff08;三&#xff09; 数据分层的压缩方式选择&#xff08;四&#xff09;开启Map输出阶段压缩&#xff08;五&#xff09;开启Reduce输出阶段压缩二、 Hive的数据存储格式…

再有人问你kafka 把这篇扔给他(建议收藏)

前言 最近重新读起kafka的内容&#xff0c;看到kafka官网文档里&#xff0c;有专门一栏讲kafka的设计&#xff0c;觉得很受益。我们常常会知道这个中间件是什么&#xff0c;是什么机制&#xff0c;这次想换个角度来聊&#xff0c;它在设计消息系统的时候&#xff0c;都做了哪些…