图论——环检测
环检测以及拓扑排序
- 前言
- 复习模版
- 环检测-DFS版本
- 环检测- BFS版本
前言
我觉得学习这些之前,一定要对图的数据结构和抽象模型有概念,并且图构建的代码模版应该手到擒来,不然还是挺折磨的,不是这差一点就是那差一点,写道力扣卡卡的非常烦人.
复习模版
我觉得单拿出来再说这个模版一点也不冗余,一定要背会死记住!并且要理解它们的数据结构
- 邻接表形式存储图
返回值类型,参数有哪些,如何构建边的关系,非常重要.
既然是构建图,那么我们的返回值一定是图没毛病吧,我们是用邻接表的形式,所以返回值类型就是List [].
对于参数而言:
a. 我们要确认这个图有几个节点,不然没办法创建节点.
b. 我们要知道节点和边的关系,一般给你的是二维的数据,里面有节点和边的关系
如何构建边的关系呢?
如果给你的是一个二维数组,你看题意是怎么说的.
我拿课程表这个题给你举例子,[0,1]表示:想学习课程0,就要先完成课程1.
指向由你自己选择,我这边选择from是1,to是0.
指向关系是from指向to.
那如何添加到邻接表里,就看你对邻接表数据结构的理解,角码代表节点,里面的值代表这个节点指向哪个节点.
代码实现如下:
List<Integer> buildGraph(int numCourses, int[][] prerequisites){List<Integer>[] graph = new LinkedList[numCourses];for(int i=0;i<numCourses;i++){graph[i] = new LinkedList<>();}for(int[] edge: prerequisites){int from = edge[1],to = edge[0];graph[from].add(to);}
}
环检测-DFS版本
构建图的模版我就不写了,上面是有的.
上一章我们聊过图论中和树不一样的地方在于图可能是会有环的.
不做任何处理的话可能会造成死循环.
处理的方式就是记录下来我哪些节点已经遍历过了,如果已经遍历了就不能再遍历,并标记是有环了.
那么我们需要两个小伙伴帮我们记录:
- boolean[] onPath - 记录递归遍历过哪些节点了
- boolean hasCycle - 图中是否有环
应该很好理解吧,代码如下:
class Solution {// 记录递归堆栈中的节点boolean[] onPath;// 记录图中是否有环boolean hasCycle = false;public boolean canFinish(int numCourses, int[][] prerequisites) {List<Integer>[] graph = buildGraph(numCourses, prerequisites);onPath = new boolean[numCourses];for (int i = 0; i < numCourses; i++) {// 遍历图中的所有节点traverse(graph, i);}// 只要没有循环依赖可以完成所有课程return !hasCycle;}// 图遍历函数,遍历所有路径void traverse(List<Integer>[] graph, int s) {if (hasCycle) {// 如果已经找到了环,也不用再遍历了return;}if (onPath[s]) {// s 已经在递归路径上,说明成环了hasCycle = true;return;}// 前序代码位置onPath[s] = true;for (int t : graph[s]) {traverse(graph, t);}// 后序代码位置onPath[s] = false;}List<Integer>[] buildGraph(int numCourses, int[][] prerequisites) {// 代码见前文}
}
关于上面遍历代码我想补充几点:
- 我们是要对每个节点都进行递归遍历,而不是只遍历一次,我刚开始学图的时候老是忘记处理这点.因为和树不一样,树只有一个起点,但是图可能有多个连通分量,而树只有一个!
- onPath数组的处理要理解代表的含义,在onPath的角码就是代表哪个节点.onPath[i]的意思就是i节点是否成环.
但是上面是有冗余计算的,假如我以3节点为起点遍历了所有可达路径,没有发现环,那么我又以5为起点,遍历到3节点,我还是要再去遍历一遍3节点.这就非常难受了.
所以我们不妨再找一个小伙伴帮忙记一下,让我们知道已经遍历过哪些节点,就不需要再遍历了
boolean[] visited 就是干这个的.
visited[i] 的意义是i节点是否被遍历过.
很简单吧,代码如下:
class Solution {// 记录一次递归堆栈中的节点boolean[] onPath;// 记录节点是否被遍历过boolean[] visited;// 记录图中是否有环boolean hasCycle = false;public boolean canFinish(int numCourses, int[][] prerequisites) {List<Integer>[] graph = buildGraph(numCourses, prerequisites);onPath = new boolean[numCourses];visited = new boolean[numCourses];for (int i = 0; i < numCourses; i++) {// 遍历图中的所有节点traverse(graph, i);}// 只要没有循环依赖可以完成所有课程return !hasCycle;}// 图遍历函数,遍历所有路径void traverse(List<Integer>[] graph, int s) {if (hasCycle) {// 如果已经找到了环,也不用再遍历了return;}if (onPath[s]) {// s 已经在递归路径上,说明成环了hasCycle = true;return;}if (visited[s]) {// 不用再重复遍历已遍历过的节点return;}// 前序代码位置visited[s] = true;onPath[s] = true;for (int t : graph[s]) {traverse(graph, t);}// 后序代码位置onPath[s] = false;}List<Integer>[] buildGraph(int numCourses, int[][] prerequisites) {// 代码见前文}
}
环检测- BFS版本
DFS是通过数组来判定是否成环,但是BFS不能这样了,因为它没办法撤回,只能往下走.
那我们可以通过每个节点的入度来实现.
所谓「出度」和「入度」是「有向图」中的概念,很直观:如果一个节点 x 有 a 条边指向别的节点,同时被 b 条边所指,则称节点 x 的出度为 a,入度为 b。
那既然说我们依赖节点的入度,它一定是被事先构建好的,所以我们除了要写一个构建图的代码,还要再写一段构建入度的代码.
上面也说了,判定入度是根据边的指向.
那么我们肯定是要循环一遍我们构建的图,然后根据节点的边去设置每个节点的入度.
代码如下:
int[] indegree = new int[numCourses];for(int[] edge: prerequisites){int from = edge[1], to = edge[0];//注意这里计算的是入度,所以脚码是toindegree[to]++;
}
因为是BFS,所以我们用队列去处理,那这个队列的话我们如何去管理呢?
首先我们肯定要往队列里面添加初始节点,我们怎么判断哪个节点是初始节点?
记住我们的核心出装: 根据入度来判定,入度>0,则代表它是中间节点;入度=0,代表是初始节点.所以我们选择遍历到入度为0 的节点添加到队列里面.
Queue<Integer> q = new LinkedList<>();
for(int i =0; i<numCourses;i++){if(indegree[i] == 0){q.offer(i);}
}
遍历如何处理呢?
✅ 终止条件: 队列为空时,表示所有可以遍历的节点都已经处理完,循环结束。
✅ 遍历方式: 每次从队列中弹出一个入度为 0 的节点,遍历它能指向的所有节点,并更新入度。
✅ 入度变为 0 时入队: 说明当前节点的所有前置依赖都已被处理完,可以加入队列,等待后续遍历。
while(!q.isEmpty()){int cur = q.poll();for(int next: graph[cur]){indegree[next]--;if(indegree[next]==0{q.offer(next);}}
}
遍历完后,那我怎么知道是不是有环呢?
我们思考一下,如果有环的话.在环中的入度可能为0吗?
我们想一下,从正常的节点,遍历到有环的节点,有环的节点会被添加进去吗?
答案是不会的,因为有环的话我们无法消除这个节点的入度呀.(不理解的简单画个图就理解了,很简单的道理)
我一个节点去遍历到下一个节点,只能把下一个节点的度-1,但你想想这个-1减的是谁的1,是下一个环对上一个环的依赖,不是下一个环对下下一个环的依赖!
所以有环的节点不会被加入进去,那么我们在遍历的时候就会少遍历一个.
所以所以!
我们就记录一下遍历的节点个数,和最后的节点比对一下,如果不相等,那么就代表有环!
ok,整个流程就是这样,代码如下,细细品味吧
class Solution {public boolean canFinish(int numCourses, int[][] prerequisites) {// 建图,有向边代表「被依赖」关系List<Integer>[] graph = buildGraph(numCourses, prerequisites);// 构建入度数组int[] indegree = new int[numCourses];for (int[] edge : prerequisites) {int from = edge[1], to = edge[0];// 节点 to 的入度加一indegree[to]++;}// 根据入度初始化队列中的节点Queue<Integer> q = new LinkedList<>();for (int i = 0; i < numCourses; i++) {if (indegree[i] == 0) {// 节点 i 没有入度,即没有依赖的节点// 可以作为拓扑排序的起点,加入队列q.offer(i);}}// 记录遍历的节点个数int count = 0;// 开始执行 BFS 循环while (!q.isEmpty()) {// 弹出节点 cur,并将它指向的节点的入度减一int cur = q.poll();count++;for (int next : graph[cur]) {indegree[next]--;if (indegree[next] == 0) {// 如果入度变为 0,说明 next 依赖的节点都已被遍历q.offer(next);}}}// 如果所有节点都被遍历过,说明不成环return count == numCourses;}// 建图函数List<Integer>[] buildGraph(int n, int[][] edges) {// 见前文}
}
相关文章:
图论——环检测
环检测以及拓扑排序 前言复习模版环检测-DFS版本环检测- BFS版本 前言 我觉得学习这些之前,一定要对图的数据结构和抽象模型有概念,并且图构建的代码模版应该手到擒来,不然还是挺折磨的,不是这差一点就是那差一点,写道力扣卡卡的非常烦人. 复习模版 我觉得单拿出来再说这个模…...
Chapter2:C#基本数据类型
参考书籍:《C#边做边学》; 2.C#基本数据类型 2.1 变量与常量 变量是程序运行过程中用于存放数据的存储单元,变量的值的程序运行过程中可以改变; 变量定义: 定义变量时,必须给每个变量起名,通过…...

kafka服务端之控制器
文章目录 概述控制器的选举与故障恢复控制器的选举故障恢复 优雅关闭分区leader的选举 概述 在Kafka集群中会有一个或多个broker,其中有一个broker会被选举为控制器(Kafka Controler),它负责管理整个集群中所有分区和副本的状态。…...

Unity笔试常考
线程同步的几种方式 1.信号量pv操作 2.互斥加锁 3.条件变量 五层网络协议指的是哪五层 1.应用层 2.运输层 3.网络层 4.链路层 5.物理层 TCP和UDP区别 tcp 面向连接,保证发送顺序,速度慢,必须在线,三次握手,4次挥手…...
移植BOA服务器到GEC2440开发板
所需软件:boa-0.94.13.tar.tar(下载:http://www.boa.org/boa-0.94.13.tar.gz) 步骤: 设置好交叉编译工具链。 1、解压下载好的压缩包(tar xzvf boa-0.94.13.tar.tar),并进入解压后的目录(cd boa-0.94.13),再进行如下操作: 先进入到src目录(下面操作都是在该目录下进行…...

WPS如何接入DeepSeek(通过第三方工具)
WPS如何接入DeepSeek 一、下载并安装OfficeAI插件二、配置OfficeAI插件三、使用DeepSeek功能 本文介绍如何通过 WPS 的第三方工具调用 DeepSeek 大模型,实现自动化文本扩写、校对和翻译等功能。 一、下载并安装OfficeAI插件 1、访问OfficeAI插件下载地址ÿ…...

【安当产品应用案例100集】037-强化OpenVPN安全防线的卓越之选——安当ASP身份认证系统
在当前数字化时代,网络安全已成为企业发展的重要组成部分。对于使用OpenVPN的企业而言,确保远程访问的安全性尤为重要。安当ASP身份认证系统凭借其强大的功能和便捷的集成方式,为OpenVPN的二次登录认证提供了理想的解决方案,特别是…...
Windows Docker笔记-制作、加载镜像
引言 在文章《Windows Docker笔记-在容器中运行项目》中,已经在容器中运行了项目。而且在这个容器中,已经调试好了项目运行的环境。 使用docker,就是为了在项目发布到生产环境时,不用再去安装项目运行的环境,直接丢给…...
leetcode_26删除有序数组中的重复项
1. 题意 给定一个重复数组,删除其中的重复项目。 2. 题解 双指针 一个指针指向有序不重复数组的最后一个数,另外一个数遍历整个数组,若两个指针对应用的数不相同,有序数组的指针右移,将数填入。 代码一 class Sol…...

速递丨DeepSeek刚刚成立香港子公司,或因考虑香港上市和招募全球AI人才
图片来源:DeepSeek 根据彭博社和财联社报道,DeepSeek 2月5日在香港成立了两家公司——DeepSeek Limited 和 DeepSeek (HK) Limited。 香港中文大学莊太量教授表示,DeepSeek进军香港将推动该市的金融科技发展。如果DeepSeek考虑在香港上市&a…...
笔灵ai写作技术浅析(六):智能改写与续写
笔灵AI写作中的智能改写和续写技术是其核心功能之一,旨在帮助用户生成高质量、多样化的文本内容。 一、智能改写技术 1. 基本原理 智能改写的目标是在保持原文语义不变的前提下,对文本进行重新表述,生成语法正确、语义连贯且风格多样的新文本。其核心思想是通过语义理解和…...

【在线优化】【有源程序】基于遗传算法(GA)和粒子群优化(PSO)算法的MPPT控制策略
目录 一、背景 二、源程序及结果 2.1 simulink仿真程序 2.2 GA模块源程序 2.3 PSO模块源程序 三、程序运行结果 3.1 基于GA优化的MPPT 3.2 基于PSO优化的MPPT 一、背景 MPPT策略能够显著提高光伏、风电等发电效率,节省大量成本。该策略的经典算法是…...
使用 Three.js 实现热力渐变效果
大家好!我是 [数擎 AI],一位热爱探索新技术的前端开发者,在这里分享前端和 Web3D、AI 技术的干货与实战经验。如果你对技术有热情,欢迎关注我的文章,我们一起成长、进步! 开发领域:前端开发 | A…...
java-异常家族梳理(流程图)
前言: 使用流程图梳理异常,便于理解 梳理: Throwable ├── Error(严重错误,无需捕获) │ ├── OutOfMemoryError │ ├── StackOverflowError │ └── ... ├── Exception(可捕获处理) │ ├── RuntimeException(非检查异常/Unchecked) │ …...

开启蓝耘之旅:DeepSeek R1 模型在智算平台的起步教程
----------------------------------------------------------我的个人主页-------------------- 动动你的手指----------------------------------------点赞👍 收藏❤--------------------------------------------------------------- 引言 在深度学习的广袤领…...
[高等数学]不定积分的概念与性质
一、知识点 (一)原函数与不定积分的概念 定义1(原函数) 如果在区间 I I I 上,可导函数 F ( x ) F(x) F(x) 的导函数为 f ( x ) f(x) f(x),即对任一 x ∈ I x\in I x∈I,都有 F ′ ( x )…...
【算法】【高精度】acwing算法基础 793. 高精度乘法
题目 给定两个非负整数(不含前导 0) A 和 B,请你计算 AB 的值。 输入格式 共两行,第一行包含整数 A,第二行包含整数 B。 输出格式 共一行,包含 AB 的值。 数据范围 1≤A的长度≤100000, 0≤B≤10000 输入样…...
sqlite 查看表结构
在SQLite中,查看表结构通常有以下几种方法: 使用.schema命令 在SQLite的命令行界面中,你可以使用.schema命令加上表名来查看该表的结构。例如,如果你想查看名为your_table_name的表结构,你可以这样做: .s…...

测试中的第一性原理:回归本质的质量思维革命
在软件工程领域,测试活动常被惯性思维和经验主义所主导——测试用例库无限膨胀、自动化脚本维护成本居高不下、测试策略与业务目标渐行渐远。要突破这种困境,第一性原理(First Principles Thinking)提供了独特的解题视角ÿ…...
flink判断两个事件之间有没有超时(不使用CEP)
1.为啥不使用cep呢,cep的超时时间设置不好配置化,无法满足扩展要求 2.超时怎么界定。A事件发生后,过了N时间,还没有收到B事件,算超时。 代码如下: import com.alibaba.fastjson.JSONObject; import lombo…...

C++实现分布式网络通信框架RPC(3)--rpc调用端
目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中,我们已经大致实现了rpc服务端的各项功能代…...

树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...
postgresql|数据库|只读用户的创建和删除(备忘)
CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...
AI编程--插件对比分析:CodeRider、GitHub Copilot及其他
AI编程插件对比分析:CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展,AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者,分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...

C++ Visual Studio 2017厂商给的源码没有.sln文件 易兆微芯片下载工具加开机动画下载。
1.先用Visual Studio 2017打开Yichip YC31xx loader.vcxproj,再用Visual Studio 2022打开。再保侟就有.sln文件了。 易兆微芯片下载工具加开机动画下载 ExtraDownloadFile1Info.\logo.bin|0|0|10D2000|0 MFC应用兼容CMD 在BOOL CYichipYC31xxloaderDlg::OnIni…...
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南 在数字化营销时代,邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天,我们将深入解析邮件打开率、网站可用性、页面参与时…...

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列,以便知晓哪些列包含有价值的数据,…...

推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材)
推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材) 这个项目能干嘛? 使用 gemini 2.0 的 api 和 google 其他的 api 来做衍生处理 简化和优化了文生图和图生图的行为(我的最主要) 并且有一些目标检测和切割(我用不到) 视频和 imagefx 因为没 a…...

使用LangGraph和LangSmith构建多智能体人工智能系统
现在,通过组合几个较小的子智能体来创建一个强大的人工智能智能体正成为一种趋势。但这也带来了一些挑战,比如减少幻觉、管理对话流程、在测试期间留意智能体的工作方式、允许人工介入以及评估其性能。你需要进行大量的反复试验。 在这篇博客〔原作者&a…...
MySQL 部分重点知识篇
一、数据库对象 1. 主键 定义 :主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 :确保数据的完整性,便于数据的查询和管理。 示例 :在学生信息表中,学号可以作为主键ÿ…...