算法通关村-----图的基本算法

2023-09-15 11:11:24

图的实现方式

邻接矩阵

定义

邻接矩阵是一个二维数组,其中的元素表示图中节点之间的关系。通常,如果节点 i 与节点 j 之间有边(无向图)或者从节点 i 到节点 j 有边(有向图),则矩阵中的元素值为 1 或者表示边的权重值。如果没有边相连,则元素值为 0 或者一个特定的标记(通常表示无穷大)。

优点

适用于稠密图,即节点之间有很多边的情况,因为它不会浪费太多空间。支持常数时间内的边的查找操作。

缺点

对于稀疏图,它会浪费大量的空间,因为大部分元素都是 0。插入和删除边的操作相对较慢,需要修改矩阵的值。

邻接表

定义

邻接表是一种数据结构,用于表示图的每个节点及其相邻节点的列表。通常使用数组(或哈希表)和链表(或其他数据结构)的组合来实现。每个节点对应一个数组元素,该元素存储指向该节点相邻节点的链表或数组。

优点

适用于稀疏图,因为它不会浪费大量空间。插入和删除边的操作相对较快,因为只需要修改链表或数组。

缺点

查找两个节点之间是否存在边需要遍历相邻节点列表,时间复杂度取决于节点的度数。

图的遍历

深度优先搜索(DFS)

定义

DFS从一个起始节点开始,沿着一条边尽可能深地探索图,直到无法继续为止,然后回溯到上一个节点,继续深入其他分支。DFS通常使用递归或栈数据结构来实现。

步骤

从起始节点开始,将其标记为已访问。

对于当前节点,遍历其未被访问的邻居节点。

选择一个邻居节点,将其标记为已访问,并递归或使用栈继续探索该邻居节点。

重复步骤3,直到没有未被访问的邻居节点,然后回溯到上一个节点,继续探索其他邻居。

特点

深度优先搜索会一直沿着一条分支深入,直到到达最深处,然后返回并探索其他分支。使用递归时,可能导致堆栈溢出,因此对于大型图,可能需要使用非递归实现,使用显式的栈数据结构。

广度优先搜索(BFS)

定义

BFS从一个起始节点开始,首先访问其所有未被访问的邻居节点,然后按照距离从起始节点逐层扩展,直到遍历完整个图。BFS通常使用队列数据结构来实现。

步骤

从起始节点开始,将其标记为已访问,并将其加入队列。
从队列中取出一个节点,访问其所有未被访问的邻居节点,并将它们标记为已访问,并加入队列。
重复步骤2,直到队列为空。

特点

广度优先搜索首先访问离起始节点最近的节点,然后逐层扩展,确保了最短路径的搜索。使用队列来实现,确保了节点按照距离逐层访问。

最小生成树

概念

最小生成树(Minimum Spanning Tree,简称 MST)是在一个连通图中找到一棵包含图中所有节点的树,并且该树的所有边的权重之和最小的树。通俗地说,最小生成树是一棵树,它连接了图中的所有节点,并且总权重最小。Prim算法和Kruskal算法是两种常见的用于求解最小生成树问题的算法。

Prim算法

思想

Prim算法的关键思想是始终选择当前最小权重的边,以确保最小生成树的总权重最小。这种贪心策略保证了Prim算法的正确性。

步骤

选择一个起始节点:首先,从图中选择一个任意的起始节点作为最小生成树的根节点。
初始化:创建一个空的最小生成树,开始时只包含根节点。同时,创建一个优先队列(通常使用最小堆)来存储候选边,该队列初始化为空。
找到候选边:将起始节点的所有相邻边(连接到未包含在最小生成树中的节点的边)添加到优先队列中。
选择最小权重的边:从优先队列中选择权重最小的边(即最小的边权重),并将其添加到最小生成树中。
标记节点:将该边连接的节点标记为已访问或已包含在最小生成树中。
重复步骤3至5:不断重复步骤3和步骤4,选择并添加下一条权重最小的边,直到最小生成树包含了图中的所有节点。
结束:当最小生成树包含了所有节点时,算法终止。

特点

Prim算法的时间复杂度取决于优先队列的实现方式,通常为O(E + VlogV),其中E是边的数量,V是节点的数量。Prim算法在稠密图(边数量较多)上效果较好,因为优先队列中的边数量较多时,操作的时间复杂度较低。它适用于带权重的图,用于解决网络设计、电路设计、城市规划等问题。

Kruskal算法

思想

Kruskal算法的关键思想是始终选择权重最小的边,并确保最小生成树不形成环。这种贪心策略保证了Kruskal算法的正确性。

步骤

初始化:将图中的所有边按照权重从小到大排序,并将它们存储在一个边集合中。同时,创建一个空的最小生成树,开始时不包含任何边。
遍历边集合:按照排序后的顺序遍历边集合。
检查边的端点:对于当前遍历到的边,检查它的两个端点是否已经在最小生成树中。如果边的两个端点都不在最小生成树中,说明将这条边添加到最小生成树不会形成环,因此可以安全地将这条边加入最小生成树中。
添加边:将通过步骤3确定可以添加的边,加入到最小生成树中。
重复步骤2至4:不断重复遍历边集合并添加边,直到最小生成树包含了图中的所有节点或者边集合已经遍历完。
结束:当最小生成树包含了所有节点时,算法终止。

特点

Kruskal算法的时间复杂度主要取决于排序边的操作,通常为O(ElogE),其中E是边的数量。由于Kruskal算法不需要对节点的度数进行操作,因此在稠密图(边数量较多)上表现较好。它适用于带权重的图,用于解决网络设计、电路设计、城市规划等问题。需要注意的是,Kruskal算法不一定会生成唯一的最小生成树,但它保证了生成的最小生成树的总权重是最小的。如果存在多个最小生成树,它们的总权重将相等。

最短路径

概念

最短路径问题是图论中的一个经典问题,其目标是找到从一个指定的起始节点到另一个指定的目标节点之间的最短路径,即具有最小权重(距离、代价等)的路径。最短路径问题分为单源最短路径和所有节点对最短路径。

单源最短路径问题(Single-Source Shortest Path):在单源最短路径问题中,给定一个起始节点,要找到该节点到图中所有其他节点的最短路径。最著名的算法是Dijkstra算法。

所有节点对最短路径问题(All-Pairs Shortest Path):在所有节点对最短路径问题中,需要找到图中任意两个节点之间的最短路径。最著名的算法是Floyd算法。

Dijkstra算法

思想

Dijkstra算法的核心思想是使用贪心策略,即始终选择当前距离最短的节点进行探索,并逐步更新距离数组。由于它不处理负权重边,因此适用于带有非负权重边的图。

步骤

初始化:首先,创建一个集合(通常是一个优先队列或最小堆),用于存储尚未确定最短路径的节点。同时,初始化一个距离数组,用于记录从起始节点到每个节点的当前最短距离。将起始节点的距离设置为0,表示到达起始节点的距离为0,而其他节点的距离设置为无穷大(或一个足够大的值)表示尚未确定的距离。
选择最近的节点:从集合中选择距离起始节点最近的节点,并将其标记为当前的最短路径节点。通常,这个选择过程使用优先队列或最小堆来实现,以确保每次选择的是距离最近的节点。
更新距离:对于当前选定的最短路径节点,遍历其所有未被确定最短路径的邻居节点。计算从起始节点经过当前节点到邻居节点的距离,并将这个距离与之前记录的距离进行比较。如果新计算得到的距离更短,就更新邻居节点的距离。
重复步骤2和步骤3:不断重复选择最近的节点和更新距离的步骤,直到所有节点都被标记为确定最短路径或集合为空。
结束:当所有节点都被标记为确定最短路径时,算法终止,距离数组中存储的就是从起始节点到所有其他节点的最短路径。

特点

Dijkstra算法的时间复杂度通常为O(V^2),其中V是节点的数量。但如果使用优先队列或最小堆来实现,时间复杂度可以优化为O(E + VlogV),其中E是边的数量。因此,对于大型图来说,使用优先队列实现Dijkstra算法更有效率。算法的正确性得益于贪心策略,它保证了每一步都选择了当前最优的路径。

Floyd算法

思想

使用动态规划的方法,通过考虑所有可能的中间节点,逐步更新节点对之间的最短路径距离。这个算法可以处理带有正、负权重边的图,以及检测负权重环路。

步骤

初始化距离矩阵:创建一个距离矩阵(通常用二维数组表示),其中元素d[i][j]表示从节点i到节点j的最短距离。初始时,将矩阵中的对角线元素(i等于j的元素)设置为0,表示从节点i到节点i的距离为0。对于其他元素,如果存在直接的边连接节点i和节点j,则将d[i][j]设置为边的权重,否则将其设置为无穷大。
更新距离矩阵:对于每对节点i和j,以及中间节点k,检查是否存在一条路径从节点i到节点j,经过节点k,比当前的最短距离更短。具体步骤如下:
对于每一对节点i和j(i ≠ j),遍历所有中间节点k(1到n,其中n是节点的总数)。
计算从节点i经过节点k到节点j的距离:d[i][k] + d[k][j]。
如果上述距离小于当前的最短距离d[i][j],则更新d[i][j]为这个新的更短距离。
重复步骤2:不断重复更新距离矩阵的步骤,直到不再存在更短的路径为止。这意味着每个节点对之间的最短路径已经被找到。
结束:当算法完成后,距离矩阵d包含了所有节点对之间的最短路径距离。

特点

Floyd算法的时间复杂度为O(V^3),其中V是节点的数量。虽然它在时间复杂度上比Dijkstra算法慢,但Floyd-Warshall算法具有一个重要的优点,即可以一次性计算所有节点对之间的最短路径,因此非常适合于计算密集型问题和需要计算所有节点对的情况。
更多推荐

Spring 声明式事务机制

😀前言本篇是Spring声明式事务系列的第二篇介绍了Spring声明式事务机制🏠个人主页:尘觉主页🧑个人简介:大家好,我是尘觉,希望我的文章可以帮助到大家,您的满意是我的动力😉😉在csdn获奖荣誉:🏆csdn城市之星2名⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣💓Java全栈群星计划

IP地址处理攻略:数据库中的存储与转换方法

🌷🍁博主猫头虎带您GotoNewWorld.✨🍁🦄博客首页——猫头虎的博客🎐🐳《面试题大全专栏》文章图文并茂🦕生动形象🦖简单易学!欢迎大家来踩踩~🌺🌊《IDEA开发秘籍专栏》学会IDEA常用操作,工作效率翻倍~💐🌊《100天精通Golang(基础入门篇)》学会Golang语言,畅玩云原生,走遍大

GaussDB数据库SQL系列:DROP & TRUNCATE & DELETE

目录一、前言二、GaussDB的DROP&TRUNCATE&DELETE简述1、命令简述2、命令比对三、GaussDB的DROPTABLE命令及示例1、功能描述2、语法3、示例四、GaussDB的TRUNCATE命令及示例1、功能描述2、语法3、示例4、示例五、GaussDB的DELETE命令及示例1、功能描述2、注意

Vue3封装知识点(三)依赖注入:project和inject详细介绍

Vue3封装知识点(三)依赖注入:project和inject详细介绍文章目录Vue3封装知识点(三)依赖注入:project和inject详细介绍一、project和inject是什么二、为了解决什么问题三、project和inject如何使用1.provide()2.inject()3.和响应式数据配合使用4.使用

Android 12 源码分析 —— 应用层 五(SystemUI的StatusBar类的启动过程和三个窗口的创建)

Android12源码分析——应用层五(SystemUI的StatusBar类的启动过程和三个窗口的创建)更新历史日期内容12023-9-18修改关于mLightsOutNotifController的错误注释在前面的文章中,我们介绍了SystemUIApp的基本布局和基本概念。接下来,我们进入SystemUI应用的各

java面试题基础第七天

一、java面试题第七天1.throw和throws的区别?throw:用于抛出一个异常对象throws:写在方法体上面,将方法体里面的异常,抛给上层2.通过故事讲清楚NIO下面通过一个例子来讲解下。假设某银行只有10个职员。该银行的业务流程分为以下4个步骤:1)顾客填申请表(5分钟);2)职员审核(1分钟);3)职员

爬虫 — Bs4 数据解析

目录一、介绍二、使用三、Bs4对象种类1、tag:标签2、NavigableString:可导航的字符串3、BeautifulSoup:bs对象4、Comment:注释四、遍历文档树1、遍历子节点2、获取节点内容3、遍历父节点4、遍历兄弟节点五、常用方法六、CSS选择器七、案例一、介绍Bs4(beautifulsoup

初识自动驾驶技术之旅 第一课 学习笔记

​🎬岸边的风:个人主页🔥个人专栏:《VUE》《javaScript》⛺️生活的理想,就是为了理想的生活!​目录📚前言📘1.自动驾驶人才需求与挑战📘2.Apollo8.0开源平台详解📘3.如何使用Apollo学习自动驾驶[上机学习]📘4.如何使用Apollo学习自动驾驶[上车学习]📚总结📚前言讲师介绍:

股票数据分析应用之可视化图表组件

股市是市场经济的必然产物,在一个国家的金融领域之中有着举足轻重的地位。在过去,人们对于市场走势的把握主要依赖于经验和直觉,往往容易受到主观因素的影响,导致决策上出现偏差。如今,通过数据可视化呈现,便可将历年数据和市场情报进行深度挖掘、分析,从中找到规律和趋势,帮助用户做出更准确的判断。回顾2022年A股市场的表现可谓是

翻牌闯关游戏

翻牌闯关游戏3关:关卡由少至多12格、20格、30格图案:12个玩法:点击两张卡牌,图案一到即可消除掉记忆时长(秒):memoryDurationTime:5可配置:默认5提示游戏玩法:showTipsFlag:1可配置:1:判断localStorage值,仅一次提示游戏玩法2:每次游戏第一关(12格关卡)都提示游戏玩

基于Java演唱会购票系统设计实现(源码+lw+部署文档+讲解等)

博主介绍:✌全网粉丝30W+,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌🍅文末获取源码联系🍅👇🏻精彩专栏推荐订阅👇🏻不然下次找不到哟2022-2024年最全的计算机软件毕业设计选题

热文推荐