Python - 深夜数据结构与算法之 Graph

目录

一.引言

二.图的简介

1.Graph 图

2.Undirected graph 无向图

3.Directed Graph 有向图

4.DFS / BFS 遍历

三.经典算法实战

1.Num-Islands [200]

2.Land-Perimeter [463]

3.Largest-Island [827]

四.总结


一.引言

Graph 无论是应用还是算法题目在日常生活中比较少见,但是其应用非常广泛,现在社交关系网络很多内容都是基于 Graph 构建的,例如我们常见的各类 GraphEmbedding,其就是基于社交关系网络图进行游走生成对应 Node Embedding。

二.图的简介

1.Graph 图

由点和边构成的数据结构即可称之为图,其在代码中一班以 Graph(V, E) 来表示,其中 V 代表点,是顶点的有穷非空集合,E 代表边,是 V 中顶点偶对的有穷集合,偶对我们可以理解为每一条边都有左右两个端点,所以需要偶对。点有入度和出度之分,即从该点出发有几条路径,如果是无向图二者相等。边除了方向外,有的场景还有权重,例如社交场景评估两个相邻用户的亲密度 E 就是有权重的,默认情况下所有边的权重均为 1,即众生平等。

2.Undirected graph 无向图

◆ 无向无权图

图一般使用邻接矩阵或邻接列表的形式表示点之间的关系,边的关系则蕴含在两点之间。

◆ 无向有权图

默认情况下,边的权重都唯一,实际工业场景下,边之间往往通过 weight 来描述两个点之间的关联程度,如果使用邻接矩阵,那么直接在对应位置赋值 weight 即可,如果是邻接列表则可以使用一个二元组进行表示。注意: 不论是无向有权还是无向无权,其对应的邻接矩阵是对称矩阵,因为 0-1 相连必然 1-0 也相连。

3.Directed Graph 有向图

有向图只会在存在的方向上表示,例如 0->1,那么邻接矩阵 [0, 1] 是有值的,但 [1, 0] 为 0,此时邻接矩阵就不在是对称矩阵,除非每个边都是双向的。有向有权图可以参考上面的无向有权图,我们只需给矩阵修改 weights,数组转换为二元组即可。 

4.DFS / BFS 遍历

◆ DFS

深度优先遍历,这里与二叉树的遍历有一点不同是图可能存在环,从而导致元素重复,所以需要进行节点的去重。其实现遵循递归,也满足递归的三要素:

- 边界条件 判断节点是否被访问

- 处理逻辑 添加当前处理的点

- 自身调用 继续处理未处理的点 

处理中不断向 next_node 出发,所以是深度优先。

◆ BFS

广度优先遍历,其会向两边扩展搜索,二叉树的层数遍历就可以通过 BFS 实现,下面方法中 generate_related_nodes 其实就是搜索两边或者周围节点的伪代码,后续我们会带来 DFS、BFS 更详细的介绍和代码。大家在这里只要明确图遍历中,DFS 和 BFS 是很重要的两种方法即可。

三.经典算法实战

1.Num-Islands [200]

岛屿数量: https://leetcode.cn/problems/number-of-islands/

◆ 题目分析

海岛的形态与数字 0、1 的关系,我们需要遍历一个点周围的情况判断,以 [0, 1] 的点为例,我们以其为起点进行遍历,对于二叉树而言,其拥有 left 和 right 两个遍历方向,而对于网格问题,其拥有上下左右四个方向,遇到 1 就继续遍历,遇到 0 则停止遍历,当无法继续扩展时,代表遍历完成,此时就生成一个岛屿。这里还有一个问题,就是 for 循环遍历岛中的节点,会有重复的情况,例如遍历 [0, 1] 和 [1, 1] 的点都可以生成岛屿 1,所以这里我们还要对遍历过的岛屿进行标记,防止其他节点再扩展,这样重复的问题也可以解决。

至于上下左右的界定,我们针对 (x, y) ± 1 即可,需要注意出格的行为,即 (x, y) 超出岛屿范围。和二叉树类比的话,停止条件就是遇到 0 或者遇到访问过的点 Visted Point 或者出格 Out of Bound,就好比是 root == None;而遍历 left、rigth 则变成 (r-1, c)、.....、(r+1, c)。

◆ DFS

class Solution(object):def numIslands(self, grid):""":type grid: List[List[str]]:rtype: int"""# 异常情况if not grid:return 0# 记录岛屿数量count = 0# 遍历岛屿for row in range(len(grid)):for col in range(len(grid[0])):if grid[row][col] == "1":# 沿 (row, col) 递归遍历上下左右self.dfs(grid, row, col)count += 1return countdef inArea(self, grid, row, col):return 0 <= row < len(grid) and 0 <= col < len(grid[0])def dfs(self, grid, row, col):# 不在网格 -> 返回if not self.inArea(grid, row, col):return# 不是岛屿->返回        if grid[row][col] != "1":return# 已遍历标记grid[row][col] = "2"self.dfs(grid, row - 1, col) # 上self.dfs(grid, row + 1, col) # 下self.dfs(grid, row, col - 1) # 左self.dfs(grid, row, col + 1) # 右

inArea 函数负责判断当前点是否在 grid 网格区域内,超出网格则停止 DFS,其次针对遍历过的 "1" 即陆地,为了防止 DFS 重复循环,我们需要将其换一个标记从而避免重复,最后上下左右探索即可。每探索一次,如果能够发现陆地则陆地全部被标记为已走,则下次无法遍历,从而划分出一块一块土地。

如上图所示,遍历到 [0, 1] 位置时会扩展出岛屿1,遍历到 [0, 3] 位置时扩展出岛屿 2,以此类推,每次 count += 1 即可,当 grid 里没有 "1" 即陆地时,遍历结束。

2.Land-Perimeter [463]

岛屿的周长: https://leetcode.cn/problems/island-perimeter/description/

◆ 题目分析 

和上题很像,也是 grid 中的岛屿,这不过这里有一个限定即只有一个岛屿,所以我们 BFS 一次就能 get 到整个岛屿。观察图像可以发现,对于 (row, col) 而言如果下一个点在海里或者 grid 外,这里便存在一个边即属于周长的边,画个图理解下,红色箭头是出 grid 的边,蓝色箭头是入海流,正所谓红头依山尽,蓝头入海流:

◆ DFS

class Solution(object):def islandPerimeter(self, grid):""":type grid: List[List[int]]:rtype: int"""# 寻找小岛的起点for row in range(len(grid)):for col in range(len(grid[0])):if grid[row][col] == 1:return self.dfs(grid, row, col)return 0# 是否在 grid 网格内def inArea(self, grid, row, col):return 0 <= row < len(grid) and 0 <= col < len(grid[0])def dfs(self, grid, row, col):# 蓝色箭头 -> 出格 -> 边长 + 1if not self.inArea(grid, row, col):return 1# 红色箭头 -> 出海 -> 边长 + 1if grid[row][col] == 0:return 1# 只剩 == "2" 的已经遍历的情况了,忽略if grid[row][col] != 1:return 0grid[row][col] = 2return self.dfs(grid, row - 1, col) + self.dfs(grid, row + 1, col) + self.dfs(grid, row, col - 1) + self.dfs(grid, row, col + 1)

红箭头出海,蓝箭头出格,这两个情况 +1 其余情况说拜拜即可,dfs 遍历思路与上面相同,也是上下左右出发。

3.Largest-Island [827]

造海填路问题: https://leetcode.cn/problems/making-a-large-island/

◆ 题目分析 

grid 网格内有多个岛屿,通过将一块海洋改变陆地,求填海造地后最大的面积,这里我们可以借助第一题的思路,先把陆地都照出来,然后遍历 "0" 即海洋,看哪个海洋变成陆地后,可以连接更多土地。 除此之外,我们还需要对相连的陆地给与记号,保证面积不会重复累加。

◆ DFS

#!/usr/bin/python
# -*- coding: UTF-8 -*-class Solution(object):def largestIsland(self, grid):""":type grid: List[List[int]]:rtype: int"""if not grid:return 0# 标记访问节点,记录每个岛屿面积index = 2location = {}visited = set()for row in range(len(grid)):for col in range(len(grid[0])):# 每块陆地标记为一个记号if grid[row][col] == 1:self.dfs(grid, row, col, index, location, visited)index += 1# 通过原始岛屿信息更新 maxif location:max_island = max(location.values())else:max_island = 0for row in range(len(grid)):for col in range(len(grid[0])):# 找到海洋并计算联通面积if grid[row][col] == 0:max_island = max(self.getAroundArea(grid, row, col, location), max_island)return max_islanddef getAroundArea(self, grid, row, col, location):# 记录最大面积res = 1# 同一块陆地算一次area = set()# 上下左右找陆地if self.inArea(grid, row + 1, col) and grid[row + 1][col] != 0:area.add(grid[row + 1][col])if self.inArea(grid, row - 1, col) and grid[row - 1][col] != 0:area.add(grid[row - 1][col])if self.inArea(grid, row, col + 1) and grid[row][col + 1] != 0:area.add(grid[row][col + 1])if self.inArea(grid, row, col - 1) and grid[row][col - 1] != 0:area.add(grid[row][col - 1])# 土地去重相加if location and area:for index in area:res += location[index]return resdef dfs(self, grid, row, col, index, location, visted):# 不在网格 -> 返回if not self.inArea(grid, row, col):return# 不是岛屿->返回if grid[row][col] != 1:return# 已遍历标记grid[row][col] = indexif index not in location:location[index] = 0if (row, col) not in visted:visted.add((row, col))location[index] += 1self.dfs(grid, row - 1, col, index, location, visted)  # 上self.dfs(grid, row + 1, col, index, location, visted)  # 下self.dfs(grid, row, col - 1, index, location, visted)  # 左self.dfs(grid, row, col + 1, index, location, visted)  # 右# 是否在 grid 网格内def inArea(self, grid, row, col):return 0 <= row < len(grid) and 0 <= col < len(grid[0])

dfs 负责在 grid 中寻找土地并标记,getAroundArea 负责遍历每一片海,并在海的上下左右寻找可能存在的岛屿,如果存在则将此地造海 res = 1,再加上发现的陆地的面积,最后更新 max 即可。这个方法的优点是思路很好理解,但是本题边界情况很多,需要判断的空 set、dict 也很多,而且遍历多次时间复杂度也较高。

四.总结

上面介绍了图 Graph<V,E> 的一般概念,以及通过 BFS 解决图的一些相关问题,图的题目整体考察不多,但是 DFS、BFS 的用法还是要熟悉。这里关于上面图 DFS 遍历的算法,推荐大家参考乐扣大神的题解,思路非常清晰: 岛屿相关问题 DFS 思路。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

方舟开发框架(ArkUI)概述

目录 1、基本概念 2、两种开发范式 3、开发框架的特性 4、UI开发&#xff08;ArkTS声明式开发范式&#xff09;概述 4.1、特点 4.2、整体架构 4.3、开发流程 方舟开发框架&#xff08;简称ArkUI&#xff09;为HarmonyOS应用的UI开发提供了完整的基础设施&#xff0c;包…

代驾系统开发:驶向未来的智能交通服务

随着科技的迅速发展&#xff0c;代驾系统的开发成为改善出行体验和提升交通服务智能化的重要一环。本文将聚焦于代驾系统开发的技术创新&#xff0c;为读者呈现其中涉及的一些令人振奋的技术代码。 1. 区块链技术的运用&#xff1a; 区块链技术被引入代驾系统&#xff0c;可…

智能优化算法应用:基于广义正态分布算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于广义正态分布算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于广义正态分布算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.广义正态分布算法4.实验参数设定…

轻量Http客户端工具VSCode和IDEA

文章目录 前言Visual Studio Code 的插件 REST Client编写第一个案例进阶&#xff0c;设置变量进阶&#xff0c;设置Token IntelliJ IDEA 的 HTTP请求构建http脚本HTTP的环境配置结果值暂存 前言 作为一个WEB工程师&#xff0c;在日常的使用过程中&#xff0c;HTTP请求是必不可…

SLAM算法与工程实践——SLAM基本库的安装与使用(6):g2o优化库(4)构建g2o的边

SLAM算法与工程实践系列文章 下面是SLAM算法与工程实践系列文章的总链接&#xff0c;本人发表这个系列的文章链接均收录于此 SLAM算法与工程实践系列文章链接 下面是专栏地址&#xff1a; SLAM算法与工程实践系列专栏 文章目录 SLAM算法与工程实践系列文章SLAM算法与工程实践…

在MacOS上Qt配置OpenCV并进行测试

目录 一.Qt环境准备 二.在Qt项目中加载Opencv库并编写代码测试 1.使用Opencv加载图片 &#xff08;1&#xff09;在Qt中创建一个新项目 &#xff08;2&#xff09;在.pro文件中链接OpenCV库 &#xff08;3&#xff09;添加新资源文件 &#xff08;4&#xff09;在mainw…

Vue 3 Composition API:让组件开发更高效、灵活(上)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

图解二叉树的Morris(莫里斯)遍历

二叉树的Morris(莫里斯)遍历 本文参考链接&#xff1a;https://leetcode.cn/problems/binary-tree-preorder-traversal/submissions/490846864/ 文章目录 二叉树的Morris(莫里斯)遍历模板代码前序遍历中序遍历后序遍历 Morris 遍历使用二叉树节点中大量指向 null 的指针&…

编程规范:长函数的思考

在工作&#xff0c;我们应该都不想看到非常的长函数。对于一个运行5年左右的项目&#xff0c;极有可能出现这种情况。由于长函数的长、if/else嵌套&#xff0c;导致代码的可读性非常差&#xff0c;这对于项目的维护和开发带来了极大的困难。所以我们应该避免写长函数&#xff0…

人工智能_机器学习070_SVM支持向量机_软间隔及优化_硬间隔_衡量间隔软度_引入松弛变量_理解隔离参数---人工智能工作笔记0110

我们继续说,之前说的C是什么意思? 我们在这个软间隔优化中就可以引出C 可以看到之前我们讨论的问题,都是基于样本点的,完全的线性可分的问题,我们称为硬间隔 可以看到这种,一分就可以,分开,简单分割就可以分开的数据,我们称之为硬间隔 但是可以看到上面这种情况,无论怎么分,都…

第1课 配置FFmpeg+OpenCV开发环境

本教程所对应的SDK下载链接&#xff1a; https://download.csdn.net/download/XiBuQiuChong/88657539 本课对应源文件下载链接&#xff1a; https://download.csdn.net/download/XiBuQiuChong/88657528 一、配置开发环境 1.下载FFmpegOpenCV开发所用的SDK压缩包&#xff0…

分享70个Java源码总有一个是你想要的

分享70个Java源码总有一个是你想要的 学习知识费力气&#xff0c;收集整理更不易。 知识付费甚欢喜&#xff0c;为咱码农谋福利。 链接&#xff1a;https://pan.baidu.com/s/1s8ZVYHb5B1GgXMlpG-6-Iw?pwd6666 提取码&#xff1a;6666 项目名称 admin、cms、console 等多…

构建创新学习体验:企业培训系统技术深度解析

企业培训系统在现代企业中发挥着越来越重要的作用&#xff0c;它不仅仅是传统培训的延伸&#xff0c;更是技术创新的结晶。本文将深入探讨企业培训系统的关键技术特点&#xff0c;并通过一些简单的代码示例&#xff0c;展示如何在实际项目中应用这些技术。 1. 前端技术&#…

Redis基础-Redis概念及常见命令

1.nosql数据库 NoSQL数据库是一种提供了非关系型数据存储的数据库系统&#xff0c;与传统的关系型数据库&#xff08;如SQL数据库&#xff09;不同。NoSQL数据库的特点是灵活性高&#xff0c;能够处理结构化、半结构化或非结构化数据。它们通常用于大数据和实时Web应用。NoSQL数…

C++面试宝典第9题:找出第K大元素

题目 给定一个整数数组a,同时给定它的大小N和要找的K(1 <= K <= N),请根据快速排序的思路,找出数组中第K大的数(保证答案存在)。比如:数组a为[50, 23, 66, 18, 72],数组大小N为5,K为3,则第K大的数为50。 解析 这道题主要考察应聘者对于快速排序的理解,以及实…

Python 数据分析 Matplotlib篇 时间序列数据绘制折线图(第4讲)

Python 数据分析 Matplotlib篇 时间序列数据绘制折线图(第4讲)         🍹博主 侯小啾 感谢您的支持与信赖。☀️ 🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹…

SAE 2.0,让容器化应用开发更简单

作者&#xff1a;邵丹 云原生容器化应用托管模式的演变 云原生这个概念从提出&#xff0c;到壮大&#xff0c;再到今天的极大普及&#xff0c;始终处于一个不断演进和革新的过程中。云原生体系下应用的托管形态是随着企业应用架构在不断演进的。最早的应用大多是集中式、单体…

Bellman_Ford算法总结

知识概览 Bellman_Ford算法适合解决存在负权边的最短路问题&#xff0c;时间复杂度为O(nm)。在存在负权边的最短路问题中&#xff0c;Bellman_Ford算法的效率虽然不如SPFA算法&#xff0c;但是Bellman_Ford算法能解决SPFA算法不能解决的经过不超过k条边的最短路问题。 例题展示…

【c++、数据结构课设】哈夫曼树

时间过的真快&#xff0c;转眼之间一个学期即将结束&#xff0c;想必这个时候大家都在准备各科的课设作业&#xff0c;本期内容是我的数据结构课设&#xff0c;希望能给大家带来帮助&#xff0c;如果有任何不足或需要改进的地方&#xff0c;欢迎各位提出宝贵的意见。 屏幕录制2…

bean生命周期源码(三)

书接上文 文章目录 一、Bean的销毁逻辑1. 简介2. Bean销毁逻辑的注册3. Bean的销毁过程 一、Bean的销毁逻辑 1. 简介 前面我们已经分析完了Spring创建Bean的整个过程的源码&#xff0c;在创建bean的核心方法中doCreateBean这一个核心方法中&#xff0c;在方法的最后面有这么…