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.mzph.cn/news/514169.shtml

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

相关文章

Docker Desktop宣布收费;腾讯7月已申请注册WECHAT CLOUD商标;MongoDB成为当前最具价值开源软件公司...

NEWS本周新闻回顾Docker Desktop 宣布收费近日 Docker 官方宣布一项新的动作&#xff0c;即将产品订阅划分为个人、专业、团队和商业不同版本。如果企业规模在 250 名员工以上或年收入超过 1000 万美元的公司想要使用 Docker Desktop&#xff0c;那么必须使用付费订阅。付费订阅…

Serverless 2.0,鸡蛋还是银弹?

简介&#xff1a; 本篇旨在介绍 Serverless 如今应用到应用&#xff08;非病句&#xff09;的各种困境&#xff0c;以及帮助用户如何去规避一些问题&#xff0c;提前了解方向。 浪潮 从 2014 年 Serverless 冒头至今&#xff0c;已经有无数的勇士在前面探路&#xff0c;阿里、…

基础组件完善的今天,如何通过业务组件提效?

简介&#xff1a; 无论是在前端刀耕火种的 jQuery/YUI 时代&#xff0c;还是到现在基于数据驱动 UI 的 React/Vue 时代&#xff0c;物料/组件一直是前端永恒的话题。基于大量重复逻辑的封装可以很显而易见地提升前端 UI 的构建效率&#xff0c;简单而直接&#xff0c;因此无论技…

​做安全操作系统,这位技术老兵是认真的!

受访者 | 王文东记者 | 伍杏玲出品 | CSDN云计算&#xff08;ID&#xff1a;CSDNcloud&#xff09;近年来国际形势变化莫测&#xff0c;基础软件作为建设国家信息系统的核心&#xff0c;其自主研发能力备受关注与热议。作为企业和个人开发者&#xff0c;我们如何打破当前国产基…

基于 Flink SQL 构建流批一体的 ETL 数据集成

简介&#xff1a; 如何利用 Flink SQL 构建流批一体的 ETL 数据集成。 本文整理自云邪、雪尽在 Flink Forward Asia 2020 的分享&#xff0c;该分享以 4 个章节来详细介绍如何利用 Flink SQL 构建流批一体的 ETL 数据集成, 文章的主要内容如下&#xff1a; 数据仓库与数据集成…

入选 SIGMOD2021 的时间序列多周期检测通用框架 RobustPeriod 如何支撑阿里业务场景?

简介&#xff1a; 本文除了介绍RobustPeriod的核心技术亮点&#xff0c;还将重点解释如何将它构筑成服务来解决阿里云的业务痛点。 近日&#xff0c;由阿里云计算平台和阿里云达摩院合作的时序多周期检测相关论文RobustPeriod: Robust Time-Frequency Mining for Multiple Peri…

《新程序员002》图书正式上市! 从“新数据库时代”到“软件定义汽车”

20年前&#xff0c;伴随着互联网打开信息化大门&#xff0c;技术人成为新时代的开拓者。在时代的召唤下&#xff0c;CSDN于2001年推出国内首个面向IT人员的专业杂志——《程序员》&#xff0c;成为一代代开发者的技术启蒙。20年后的今天&#xff0c;人工智能、云计算、大数据等…

Kubernetes 稳定性保障手册 -- 极简版

简介&#xff1a; Kubernetes 在生产环境中的采用率越来越高&#xff0c;复杂度越来越高&#xff0c;由此带来的稳定性保障的挑战越来越大。 Kubernetes 在生产环境中的采用率越来越高&#xff0c;复杂度越来越高&#xff0c;由此带来的稳定性保障的挑战越来越大。 对于基于 K…

收藏!这些IDE使用技巧,你都知道吗

简介&#xff1a; 欲善其事&#xff0c;先利其器。对于研发同学&#xff0c;在日常的开发工作中&#xff0c;我们与之打交道最多的便是编程的IDE。能否高效和灵活的使用IDE&#xff0c;将对我们的工作效率起着举足轻重的作用。 一 、背景 1 、目的 欲善其事&#xff0c;先利其…

做安全操作系统,这位技术老兵是认真的!

受访者 | 王文东 记者 | 伍杏玲 出品 | CSDN云计算&#xff08;ID&#xff1a;CSDNcloud&#xff09; 近年来国际形势变化莫测&#xff0c;基础软件作为建设国家信息系统的核心&#xff0c;其自主研发能力备受关注与热议。作为企业和个人开发者&#xff0c;我们如何打破当前…

快手基于 Flink 的持续优化与实践

简介&#xff1a; 快手基于 Flink 的持续优化与实践的介绍。 一、Flink 稳定性持续优化 第一部分是 Flink 稳定性的持续优化。该部分包括两个方面&#xff0c;第一个方面&#xff0c;主要介绍快手在 Flink Kafka Connector 方面做的一些高可用&#xff0c;是基于内部的双机房读…

平台建设的7大问题:蚂蚁AI平台实践深度总结

简介&#xff1a; 在支持蚂蚁几乎所有核心业务运行和发展的过程中&#xff0c;我们在平台建设、业务支持、平台运营、AI创新以及AI整体运营等各个方面做了很多尝试&#xff0c;有了不少的收获和感悟&#xff0c;在此分享给大家。 过去几年&#xff0c;我和团队一直在负责蚂蚁集…

知乎热问:进入内核态究竟是什么意思?

‍‍知乎上有一个问题&#xff1a;进入内核态究竟是什么意思&#xff1f;暂且忘记这个问题&#xff0c;让我们从另一个问题出发&#xff0c;一步步引出这个问题的答案。特权指令问题现代计算机里面&#xff0c;同时运行了很多程序&#xff0c;比如Office软件、浏览器、QQ、还有…

我在阿里云做云开发平台

简介&#xff1a; 你体验过云上的研发模式了没&#xff1f; 各大云厂商今年在开发者阵地侧逐渐开始向“云”化开发发展&#xff0c;最为显著的产品就是Cloud IDE&#xff0c;催生出来的趋势就是云端开发。云开发现阶段尽管在各大公司内部无法作为日常开发工具普遍推广&#xff…

mysql 事务日志备份_事务日志备份与恢复 5

14.5 用Bak文件恢复到故障点的奥秘如果数据库被损坏&#xff0c;我们就只能利用备份集文件(通常扩展名为BAK)来恢复数据库&#xff0c;如果备份集中包含了尾日志备份&#xff0c;我们同样能将数据库恢复到故障点。前面我们已经介绍了使用restore headeronly命令可以查看备份集文…

从no-code到low-code:企业级hpaPaaS的未来

简介&#xff1a; 本文将简单谈一谈基于 no-code > low-code > pro-code 渐进式思路的研发体系。 引子 宜搭负责人骁勇给我举过一个例子&#xff0c;我们小时候逢年过节穿的衣服&#xff0c;都是去裁缝店选一下材料、量一下尺寸&#xff0c;等个半个来月&#xff0c;讨回…

“解救”外卖骑手,美团首次公开算法规则!

整理 | 郑丽媛出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;相信大家点外卖的时候都会注意到“预估到达时间”这一栏&#xff0c;那你是否好奇过这个时间是怎么得出来的呢&#xff1f;简单用距离除以速度&#xff1f;还是结合送餐距离、出餐时间和天气情况等更多…

Serverless 极致弹性解构在线游戏行业痛点

简介&#xff1a; 本文将通过剖析一个个具体的场景案例&#xff0c;以期望给相关的游戏开发同学带来共鸣&#xff0c;同时也希望能给非游戏行业的同学带来一些启发。 一、前言 1. 游戏客户上云关注点 游戏行业是一个富有创意又竞争激烈的市场&#xff0c;被称为第九艺术。游戏…

ACK正式支持对基于Alibaba Cloud Linux操作系统的集群进行等保加固

简介&#xff1a; 我们对基于Alibaba Cloud linux操作系统的ACK集群进行等保加固&#xff0c;意味着阿里云在云产品开发和交付的过程中将安全作为重要组成部分&#xff0c;将合规融入到产品的“血液”中&#xff0c;把安全植入产品的“骨髓”里&#xff0c;能够帮助有等保诉求的…

华为一口气发布十余款新品,HarmonyOS用户过亿

9月13日19:30&#xff0c;华为在线上举办了智慧办公新品发布会。本次发布会带来了华为MateBook 13s笔记本电脑、华为MateBook 14s笔记本电脑、华为MateStation X一体机、华为PixLab X1打印机、华为MateView GT 27英寸曲面屏显示器以及华为MatePad Pro 12.6英寸套装版等十余款新…