图_基础算法

图这种数据结构还有一些比较特殊的算法,比如二分图判断,有环图无环图的判断,拓扑排序,以及最经典的最小生成树,单源最短路径问题,更难的就是类似网络流这样的问题。

先看拓扑排序(有环无环):la总微信文章的链接:https://mp.weixin.qq.com/s?__biz=MzAxODQxMDM0Mw==&mid=2247491897&idx=1&sn=c2d77dd649548d077815af3c976b61d1&scene=21#wechat_redirect
然后看二分图
然后看并查集
然后最小生成树dijkstra 单源最短路径

基本概念:

  1. 大部分都是以邻接表的形式存储:
// 记得每一个都要初始化一下为new ArrayList<>()或者LinkedList;
List<Integer>[] graph;

拓扑排序

  1. 拓扑排序的对象,就是有向无环图(DAG)。一个有向无环图的拓扑排序结果 不止一种。

给定一个包含 n个节点的有向图 G,我们给出它的节点编号的一种排列,如果满足:

对于图 G 中的任意一条有向边 (u,v),u 在排列中都出现在 v的前面。

那么称该排列是图 G 的「拓扑排序」

在这里插入图片描述

  1. 先说一下怎么判断图有没有环(力扣207 课程表)。
    BFS很简单,直接把所有入度为0的入队列遍历一遍,adj度数减1,要是入度为0就继续入队列,最后还有度数不为0的节点(也可以每次遍历queue计数,最后判断计数结果等不等于n),就说明有环。
// 207题 BFS实现
class Solution {public boolean canFinish(int numCourses, int[][] prerequisites) {int[] indegrees = new int[numCourses];List<List<Integer>> adjacency = new ArrayList<>();Queue<Integer> queue = new LinkedList<>();// 初始化图for(int i = 0; i < numCourses; i++)adjacency.add(new ArrayList<>());// 注意cp[0]前置为cp[1],所以先上cp[1]才能继续走到cp[0],即 箭头指向为1->0for(int[] cp : prerequisites) {indegrees[cp[0]]++;adjacency.get(cp[1]).add(cp[0]);}// 把所有入度为0的加进来for(int i = 0; i < numCourses; i++)if(indegrees[i] == 0) queue.add(i);// 开始bfswhile(!queue.isEmpty()) {int pre = queue.poll();numCourses--;for(int cur : adjacency.get(pre))// 别忘了减入度if(--indegrees[cur] == 0) queue.add(cur);}// 根据n是否减为0判断是否全部节点都被遍历了一遍,如果有环就说明n不为0return numCourses == 0;}
}
dfs判断的方式更简单了,在开始进入遍历cur的adj之前 标记onPath为true,遍历完adj之后把onPath恢复为false(恢复现场)。下一次递归开始时发现onPath已经为true就说明有环,类似贪吃蛇咬到了自己。
onPath数组和visited数组可以合为一个数组,用int标识不同的情况。例如初始化flag数组都是0,然后进入递归置为1,结束递归置为-1.这样,每次进入一个节点的时候就判断如果flag==-1就返回;flag为1就说明有环。
// DFS判断是否有环
boolean[] onPath;boolean hasCycle = false;
boolean[] visited;void traverse(List<Integer>[] graph, int curIndex) {if (onPath[curIndex]) {// 发现环!!!hasCycle = true;}if (visited[curIndex]) {return;}// 将节点 s 标记为已遍历visited[curIndex] = true;// 开始遍历节点 sonPath[curIndex] = true;for (int adj : graph[curIndex]) {traverse(graph, adj);}// 节点 s 遍历完成onPath[curIndex] = false;
}

注意由于可能 一次traverse并不能遍历完所有的节点,所以要遍历nums,从0-n都当成curIndex传入。

// 207课程表 dfs实现判断是否有环,以flag为标识 return true或者false
class Solution {public boolean canFinish(int numCourses, int[][] prerequisites) {List<List<Integer>> adjacency = new ArrayList<>();for(int i = 0; i < numCourses; i++)adjacency.add(new ArrayList<>());int[] flags = new int[numCourses];for(int[] cp : prerequisites)adjacency.get(cp[1]).add(cp[0]);for(int i = 0; i < numCourses; i++)if(!dfs(adjacency, flags, i)) return false;return true;}private boolean dfs(List<List<Integer>> adjacency, int[] flags, int i) {if(flags[i] == 1) return false;if(flags[i] == -1) return true;flags[i] = 1;for(Integer j : adjacency.get(i))if(!dfs(adjacency, flags, j)) return false;flags[i] = -1;return true;}
}

然后借机说一下递归怎么进行拓扑排序,以力扣的210课程表II 为例,由于有依赖的前置课程,所以我们要先完成依赖的课程,也就是树的根节点。再代入后序遍历的思路,拓扑排序的结果其实就是这个多叉树后序遍历的数组反转之后的结果。
当然,如果要实现拓扑排序,前提一定是要先判断是否有环的。可以在遍历的时候判断,也可以直接把207的代码copy过来

boolean[] visited;
// 记录后序遍历结果
List<Integer> postorder = new ArrayList<>();int[] findOrder(int numCourses, int[][] prerequisites) {// 先保证图中无环if (!canFinish(numCourses, prerequisites)) {return new int[]{};}// 建图List<Integer>[] graph = buildGraph(numCourses, prerequisites);// 进行 DFS 遍历visited = new boolean[numCourses];for (int i = 0; i < numCourses; i++) {traverse(graph, i);}// 将后序遍历结果反转,转化成 int[] 类型Collections.reverse(postorder);int[] res = new int[numCourses];for (int i = 0; i < numCourses; i++) {res[i] = postorder.get(i);}return res;
}void traverse(List<Integer>[] graph, int s) {if (visited[s]) {return;}visited[s] = true;for (int t : graph[s]) {traverse(graph, t);}// 后序遍历位置postorder.add(s);
}

为什么310的最小高度树的解法(把入度为1也就是叶子结点入队列 然后每次去掉一圈叶子结点之后就是根节点了)是拓扑排序,可能是因为满足拓扑排序的性质:后序遍历。

并查集 Union-Find

并查集比较简单,也可以直接用并查集判断是否有环,这里直接附上并查集的代码

class UF {private int count;private int parent[];public UF(int n) {parent = new int[n+1];// 初始化的时候 全都是独立的根节点 指向自己for (int i = 0; i <= n; i++) {parent[i] = i;}// 计数countcount = n;}public int getUFCount() {return count;}public int findRoot(int x) {// 注意回溯的话要用if。循环的话要用whileif (parent[x] != x) {parent[x] = findRoot(parent[x]);}return parent[x];/*while (parent[x] != x) {// 进行路径压缩parent[x] = parent[parent[x]];x = parent[x];}return x;*/}public void union(int a, int b) {int rootA = findRoot(a);int rootB = findRoot(b);if (rootA == rootB) {return;}parent[rootA] = rootB;// 别忘了count--count--;}public boolean connected(int a, int b) {return findRoot(a) == findRoot(b);}}

并查集相关题目:
785 判断二分图
1319 连通网络的操作次数 这题用UF做可以;用DFS类似于判断拓扑排序是否有环也可以,具体看一下题解。
886 可能的二分法

在这里插入图片描述

二分图

接着说一下二分图

定义:如果能将一个图的节点集合分割成两个独立的子集 A 和 B ,并使图中的每一条边的两个节点一个来自 A 集合,一个来自 B 集合,就将这个图称为 二分图 。

二分图也可以用并查集UF来解决,把所有cur的adj都union,遍历某个adj的时候如果发现adj和cur已经相连了connected了,就说明不可分为两个独立子集。
例题:785 判断二分图
还有一种就是染色法,遍历adj如果未染色就染成和cur不一致的颜色,如果发现adj颜色不是初始状态且和cur颜色一致,就说明不可二分。

染色可以初始化RED/BLUE之类的,也可以直接用int标识。例如0;1;-1

/*** BFS广度优先遍历-染色法** @param graph* @return*/private boolean useBfs(int[][] graph) {int n = graph.length;Deque<Integer> queue = new ArrayDeque<>(n);// 用visit数组表示染色,visited[i]为0表示还未被染色,初次染色为1,其邻接点染色时被赋值为-1int visited[] = new int[n];// 每个节点未被染色前都要进队列for (int i = 0; i < n; i++) {if (visited[i] != 0) {continue;}visited[i] = 1;queue.addLast(i);while (!queue.isEmpty()) {int item = queue.removeFirst();for (int adj : graph[item]) {// 未被染色 就处理为-visited[item];if (visited[adj] == 0) {visited[adj] = -visited[item];queue.addLast(adj);}// 已被染色且和当前颜色相等 就返回falseelse if (visited[adj] == visited[item]) {return false;}}}}return true;}

最小生成树

Kruskal 算法

一开始的时候就把所有的边排序,然后从权重最小的边开始挑选属于最小生成树的边,组建最小生成树。

prim算法

原理:对于任意一个节点,切分他的连接点之后,横切边上权重最小的边,一定是构成最小生成树的一条边。
实现:用优先级队列结合BFS动态获取权重最小边
为了防止重复切,需要用一个变量判断是否已经被加入过结果集(最小生成树)中了。
在这里插入图片描述

class Prim {// 核心数据结构,存储「横切边」的优先级队列private PriorityQueue<int[]> pq;// 类似 visited 数组的作用,记录哪些节点已经成为最小生成树的一部分private boolean[] inMST;// 记录最小生成树的权重和private int weightSum = 0;// graph 是用邻接表表示的一幅图,// graph[s] 记录节点 s 所有相邻的边,// 三元组 int[]{from, to, weight} 表示一条边private List<int[]>[] graph;public Prim(List<int[]>[] graph) {this.graph = graph;this.pq = new PriorityQueue<>((a, b) -> {// 按照边的权重从小到大排序return a[2] - b[2];});// 图中有 n 个节点int n = graph.length;this.inMST = new boolean[n];// 随便从一个点开始切分都可以,我们不妨从节点 0 开始inMST[0] = true;cut(0);// 不断进行切分,向最小生成树中添加边while (!pq.isEmpty()) {int[] edge = pq.poll();int to = edge[1];int weight = edge[2];if (inMST[to]) {// 节点 to 已经在最小生成树中,跳过// 否则这条边会产生环continue;}// 将边 edge 加入最小生成树weightSum += weight;inMST[to] = true;// 节点 to 加入后,进行新一轮切分,会产生更多横切边cut(to);}}// 将 s 的横切边加入优先队列private void cut(int s) {// 遍历 s 的邻边for (int[] edge : graph[s]) {int to = edge[1];if (inMST[to]) {// 相邻接点 to 已经在最小生成树中,跳过// 否则这条边会产生环continue;}// 加入横切边队列pq.offer(edge);}}// 最小生成树的权重和public int weightSum() {return weightSum;}// 判断最小生成树是否包含图中的所有节点public boolean allConnected() {for (int i = 0; i < inMST.length; i++) {if (!inMST[i]) {return false;}}return true;}
}

dijkstra最短路径

说到了这里,狄杰斯特拉算法其实非常简单,就是一个BFS算法的进阶使用,先用一个对象State保存当前节点ID、距离start节点的距离distFromStart。每次用优先级队列,按照distFromStart从小到大排序。然后遍历队列中cur的adj,把最小路径加入结果集中即可。

// 返回节点 from 到节点 to 之间的边的权重
int weight(int from, int to);// 输入节点 s 返回 s 的相邻节点
List<Integer> adj(int s);// 输入一幅图和一个起点 start,计算 start 到其他节点的最短距离
int[] dijkstra(int start, List<Integer>[] graph) {// 图中节点的个数int V = graph.length;// 记录最短路径的权重,你可以理解为 dp table// 定义:distTo[i] 的值就是节点 start 到达节点 i 的最短路径权重int[] distTo = new int[V];// 求最小值,所以 dp table 初始化为正无穷Arrays.fill(distTo, Integer.MAX_VALUE);// base case,start 到 start 的最短距离就是 0distTo[start] = 0;// 优先级队列,distFromStart 较小的排在前面Queue<State> pq = new PriorityQueue<>((a, b) -> {return a.distFromStart - b.distFromStart;});// 从起点 start 开始进行 BFSpq.offer(new State(start, 0));while (!pq.isEmpty()) {State curState = pq.poll();int curNodeID = curState.id;int curDistFromStart = curState.distFromStart;if (curDistFromStart > distTo[curNodeID]) {// 已经有一条更短的路径到达 curNode 节点了continue;}// 将 curNode 的相邻节点装入队列for (int nextNodeID : adj(curNodeID)) {// 看看从 curNode 达到 nextNode 的距离是否会更短int distToNextNode = distTo[curNodeID] + weight(curNodeID, nextNodeID);if (distTo[nextNodeID] > distToNextNode) {// 更新 dp tabledistTo[nextNodeID] = distToNextNode;// 将这个节点以及距离放入队列pq.offer(new State(nextNodeID, distToNextNode));}}}return distTo;
}

这里有一个优化点,不用visited数组判断是否会走回头路,因为每个adj都先判断加上cur-adj的权重之后是否小于之前已加入结果集的最小路径,小的话才会更新结果集并加入队列。
因为两个节点之间的最短距离(路径权重)肯定是一个确定的值,不可能无限减小下去,所以队列一定会空,不会无限循环。队列空了之后,distTo数组中记录的就是从start到其他节点的最短距离。

上述代码是为了找到从start到所有节点的最小路径(结果集为distTo[]),如果指定了到end节点,在while循环中判断curNodeId==end即可结束while循环,return curDistFromStart(因为每次从优先级队列中拿出来的一定是最小的路径权重)。

相关题目:
743 题「网络延迟时间
第 1514 题「概率最大的路径」

看一下1631 最小体力消耗路径 应该和并查集、二分、dijkstra最短路径都有关系

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

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

相关文章

【linux性能分析】heaptrack分析内存占用

文章目录 1. Heaptrack是什么2. Heaptrack有哪些功能3. Heaptrack和valgrind massif对比4. Heaptrack安装5. Heaptrack生成追踪文件6. heaptrack_gui进行内存分析7. heaptrack_print也能用于堆分析8. 报错解决9. 补充介绍&#xff1a;heaptrack编译安装 1. Heaptrack是什么 he…

内网穿透--Spp-特殊协议-上线

免责声明:本文仅做技术交流与学习... 目录 spp项目: 一图通解: 1-下载spp 2-服务端执行命令 3-客户端执行命令 4-服务端cs监听&生马 spp项目: GitHub - esrrhs/spp: A simple and powerful proxy 支持的协议&#xff1a;tcp、udp、udp、icmp、http、kcp、quic 支持的…

Java开发者必知的时间处理工具:SimpleDateFormat类详解

哈喽&#xff0c;各位小伙伴们&#xff0c;你们好呀&#xff0c;我是喵手。运营社区&#xff1a;C站/掘金/腾讯云&#xff1b;欢迎大家常来逛逛 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互相学习&#xff0c;一…

小红书云原生 Kafka 技术剖析:分层存储与弹性伸缩

面对 Kafka 规模快速增长带来的成本、效率和稳定性挑战时&#xff0c;小红书大数据存储团队采取云原生架构实践&#xff1a;通过引入冷热数据分层存储、容器化技术以及自研的负载均衡服务「Balance Control」&#xff0c;成功实现了集群存储成本的显著降低、分钟级的集群弹性迁…

[图解]SysML和EA建模住宅安全系统-07 to be块定义图

1 00:00:01,970 --> 00:00:05,040 入侵者这里有个∞ 2 00:00:05,530 --> 00:00:07,000 说明它下面已经有子图了 3 00:00:07,010 --> 00:00:08,080 我们看看里面子图 4 00:00:10,200 --> 00:00:17,000 这里&#xff0c;我们看位置 5 00:00:19,030 --> 00:00:…

Vitis HLS 学习笔记--抽象并行编程模型-不良示例

目录 1. 简介 2. 基础 kernel 2.1 pass kernel 2.2 double_pass kernel 2.3 add_kernel 2.4 split kernel 3. 三种bypass 3.1 input_bypass 3.2 middle_bypass 3.3 output_bypass 4. 总结 1. 简介 本文展示三个在数据流水线中常见的问题&#xff1a; 输入参数绕过…

chatgpt线性差值 将直线渐变颜色

color(x)(x-x1)/(x2-x1) 与gpt给出的 这个位置比例可以表示为d/L是概念相同 x-x1是计算当前点距离起点距离&#xff0c;x2-x1是计算长度 例如&#xff0c;如果我们在直线上距离起点A的距离为d&#xff0c;整条直线的长度为L 用数学方式解释 2024/5/25 18:54:30 当我们要在一…

vue+echart :点击趋势图中的某一点或是柱状图,出现弹窗,并传输数据

样式 在趋势图中点击某一个柱状图&#xff0c;出现下面的弹窗 代码实现 主要是在趋势图页面代码中&#xff0c;在初始化趋势图的设置中&#xff0c;添加对趋势图监听的点击方法 drawChart() {const chartData this.chartData;let option {};if (!chartData.xData?.len…

Swift 类和结构体

类和结构体 一、结构体和类对比1、类型定义的语法2、结构体和类的实例3、属性访问4、结构体类型的成员逐一构造器 二、结构体和枚举是值类型三、类是引用类型1、恒等运算符2、指针 结构体和类作为一种通用而又灵活的结构&#xff0c;成为了人们构建代码的基础。你可以使用定义常…

python mp3转mp4工具

成品UI 安装moviepy库 pip install moviepy 转换demo from moviepy.editor import *# 创建一个颜色剪辑&#xff0c;时长与音频相同 audioclip AudioFileClip(r"C:\Users\Administrator\PycharmProjects\pythonProject44\test4\赵照 - 灯塔守望人.mp3") videoclip…

用Python Pygame做的一些好玩的小游戏

有些游戏的代码比较长就不公布了 1.简简单单 1.疯狂的鸡哥 你要准备的图片&#xff1a; 命名为&#xff1a;ji.png 代码&#xff1a; import pygame import random as r pygame.init() pygame.display.set_caption(aaa) pm pygame.display.set_mode((800,600))class Ls(py…

Java进阶学习笔记15——接口概述

认识接口&#xff1a; Java提供了一个关键字Interface&#xff0c;用这个关键字我们可以定义一个特殊的结构&#xff1a;接口。 接口不能创建对象。 注意&#xff1a;接口不能创建对象&#xff0c;接口是用来被类实现&#xff08;implements&#xff09;的&#xff0c;实现接口…

基于Spring Boot的高校图书馆管理系统

项目和论文都有企鹅号2583550535 基于Spring Boot的图书馆管理系统||图书管理系统_哔哩哔哩_bilibili 第1章 绪论... 1 1.1 研究背景和意义... 1 1.2 国内外研究现状... 1 第2章 相关技术概述... 2 2.1 后端开发技术... 2 2.1.1 SpringBoot 2 2.1.2 MySQL.. 2 2.1.3 My…

vr商品全景展示场景编辑软件的优点

3D模型展示网站搭建编辑器以强大的3D编辑引擎和逼真的渲染效果&#xff0c;让您轻松实现模型展示的优化。让用户通过简单的操作&#xff0c;就能满足个人/设计师/商户多样化展示的需求&#xff0c;让您的模型成为独一无二的杰作。 3D模型展示网站搭建编辑器采用国内领先的实时互…

java继承使用细节二

构造器 主类是无参构造器时会默认调用 public graduate() {// TODO Auto-generated constructor stub也就是说我这里要用构造器会直接调用父类。它是默认看不到的 &#xff0c;System.out.println("graduate");} 但当主类是有参构造器如 public father_(int s,doubl…

c语言:将小写字母转换为大写字母

//将小写字母转换为大写字母 #include <stdio.h> #include <ctype.h> int main() { char arr[]"you are low"; int i0; while(arr[i]) { if(islower(arr[i])) { arr[i]arr[i]-32; } i; } printf("%s\n",arr); return 0; }

微调Llama3实现在线搜索引擎和RAG检索增强生成功能

视频中所出现的代码 Tavily SearchRAG 微调Llama3实现在线搜索引擎和RAG检索增强生成功能&#xff01;打造自己的perplexity和GPTs&#xff01;用PDF实现本地知识库_哔哩哔哩_bilibili 一.准备工作 1.安装环境 conda create --name unsloth_env python3.10 conda activate …

dubbo复习:(8)使用sentinel对服务进行降级

一、下载sentinel-dashboard控制台应用并在8080端口启动 二、项目添加springboot 和dubbo相关依赖&#xff08;降级规则并未持久化&#xff0c;如果需要持久化&#xff0c;如果需要持久化降级规则&#xff0c;只需增加nacos相关依赖并在nacos中进行配置&#xff0c;然后配置app…

使用Python Tkinter创建GUI应用程序

大家好&#xff0c;当我们谈及使用Python Tkinter创建GUI应用程序时&#xff0c;我们涉及的不仅是技术和代码&#xff0c;更是关于创造力和用户体验的故事。Tkinter作为Python标准库中最常用的GUI工具包&#xff0c;提供了丰富的功能和灵活的接口&#xff0c;让开发者能够轻松地…

【基于springboot+vue的房屋租赁系统】

介绍 本系统是基于springbootvue的房屋租赁系统&#xff0c;数据库为mysql&#xff0c;可用于日常学习和毕设&#xff0c;系统分为管理员、房东、用户&#xff0c;部分截图如下所示&#xff1a; 部分界面截图 用户 管理员 联系我 微信&#xff1a;Zzllh_