算法-图论(建图,拓扑排序)
文章目录
- 建图的三种方式
- 邻接矩阵
- 邻接表
- 链式前向星
- 拓扑排序
- 拓扑排序基础原理介绍
- 拓扑排序步骤解析
- 拓扑排序模板leetcode-课程表
建图的三种方式
我们建图的三种方式分别是邻接矩阵, 邻接矩阵, 链式前向星
邻接矩阵
假设我们的点的个数为N个, 我们就把他们的下标依次标为1, 2 ,…, 然后在一个矩阵表上进行边的添加, 比如我要在点2和点4之间添加一个权值为5的边, 那么我就在矩阵matrix[2][4] = 5即可, 唯一注意的就是如果是无向图, 就需要把matrix[4][2]位置也设置为5, 所以这个方法的弊端是十分明显的, 就是消耗的空间过大, 所以我们在大厂的笔试或者是比赛, 不会用这种方式进行建图, 下面是邻接矩阵法的代码实现
public class day23 {public static void main(String[] args) {int[][] edges = {{1,2,5},{5,3,1},{1,4,4},{2,5,2}};//测试一下使用邻接矩阵法建图GraphUseMatrix graphUseMatrix = new GraphUseMatrix();graphUseMatrix.build(5);graphUseMatrix.directGraph(edges);//graphUseMatrix.unDirectGraph(edges);graphUseMatrix.showGraph();}
}/*** 邻接矩阵建图的方法* 下面我们介绍的所用的图都是带权值的图, 不带权的更简单就不说了*/
class GraphUseMatrix{//设置一个最大的点数(根据题意)private static final int MAXN = 11;//设置一个最大的边数(根据题意, 无向图 * 2)private static final int MAXM = 21;//构建一个当前的点数curnprivate static int curN = 0;//设置一个邻接的矩阵(大小就是(点数 + 1) * (点数 + 1), 这里我们保证是够用的)private static final int[][] matrix = new int[MAXN][MAXN];//初始化矩阵的方法(传入的点的数量)public void build(int n){curN = n;//清空矩阵的脏数据for(int i = 1; i <= n; i++){for(int j = 1; j <= n; j++){//把邻接矩阵中的数据刷为最大值(因为权值可能为0)matrix[i][j] = Integer.MAX_VALUE;}}}//添加边的方法private void addEdge(int from, int to, int weight){matrix[from][to] = weight;}//在有向带权图中添加边public void directGraph(int[][] edges){for(int[] edge : edges){addEdge(edge[0], edge[1], edge[2]);}}//在无向带权图中添加边public void unDirectGraph(int[][] edges){for(int[] edge : edges){addEdge(edge[0], edge[1], edge[2]);addEdge(edge[1], edge[0], edge[2]);}}//展示图的方式public void showGraph(){System.out.println("邻接矩阵法建图展示");for(int i = 1; i <= curN; i++){for(int j = 1; j <= curN; j++){if(matrix[i][j] == Integer.MAX_VALUE){System.out.print("∞ ");}else{System.out.print(matrix[i][j] + " ");}}System.out.println();}}
}
上述代码的测试结果见下
测试结果也是很明显的, 证明我们之前的代码是没有问题的, 无向图总体是按照正对角线呈对称分布
邻接表
邻接表是一种动态的建图的方式, 关于大厂的笔试面试题, 邻接表以及完全够用了, 如果涉及到比赛内容的话, 我们会使用链式前向星建图法, 等会我们会介绍到这种算法, 说回来邻接表, 其实就是一个
ArrayList<ArrayList<int(无权)/int>的结构, 也就是顺序表套顺序表的结构, 假如我们要建立一个从点2到点4的边,如果不带权值, 我们就让外层顺序表2下标对应的顺序表添加一个元素4, 如果同时带有一个权值的话, 我们就添加一个数组(假设权值是8)我们就让2下标对应的顺序表添加一个[4,8]数组, 下面是邻接表的代码实现
public class day23 {public static void main(String[] args) {int[][] edges = {{1, 2, 5}, {5, 3, 1}, {1, 4, 4}, {2, 5, 2}};
// //测试一下使用邻接矩阵法建图
// GraphUseMatrix graphUseMatrix = new GraphUseMatrix();
// graphUseMatrix.build(5);
// graphUseMatrix.unDirectGraph(edges);
// graphUseMatrix.showGraph();//测试一下邻接表法建图GraphUseList graphUseList = new GraphUseList();graphUseList.build(5);graphUseList.directGraph(edges);//graphUseList.unDirectGraph(edges);graphUseList.showGraph();}
}/*** 邻接表建图的方法(是一种动态的结构)* 和邻接矩阵一样, 我们在这里介绍的都是带权的图*/
class GraphUseList {//构建一个当前的点数private static int curN = 0;//邻接表的主体private static ArrayList<ArrayList<int[]>> list = new ArrayList<>();//初始化邻接表(传入一个点的数量)public void build(int n) {//设置当前的点数curN = n;//上来直接清空邻接表list.clear();//开始构建顺序表(新添加n+1个列表)for (int i = 0; i <= n; i++) {list.add(new ArrayList<>());}}//添加边的方法private void addEdge(int from, int to, int weight) {list.get(from).add(new int[]{to, weight});}//构建一个有向的图public void directGraph(int[][] edges) {for (int[] edge : edges) {addEdge(edge[0], edge[1], edge[2]);}}//构建一个无向的图public void unDirectGraph(int[][] edges) {for (int[] edge : edges) {addEdge(edge[0], edge[1], edge[2]);addEdge(edge[1], edge[0], edge[2]);}}//展示邻接表图的方式public void showGraph() {System.out.println("邻接表法建图展示");for (int i = 1; i <= curN; i++) {System.out.print("点" + i + "(邻点/权值) : ");for (int[] elem : list.get(i)) {System.out.print("[" + elem[0] + "," + elem[1] + "]");}System.out.println();}}
}
执行结果如下图所示
链式前向星
前两种建图的方式都有着明显的缺点, 第一个虽然是静态空间但是空间的大小过大, 第二个虽然使用的空间不是很大但是是一种动态的结果, 就会在时间上大打折扣, 所以我们就想要一种既可以是静态空间又可以做到省空间省时间的结构, 我们就需要下面的链式前向星建图法, 这个方法有点类似与前缀树的静态空间建树法(之前有)和链表头插法的结合, 下面我们分析一下建图的过程
准备过程 :
我们准备三个数组, 把每一个加入的边都设置一个编号(从1开始逐渐增加)
数组 | 数组解释 |
---|---|
head数组 | 下标表示点的编号, head[i]表示这个点的’头边’的编号 |
next数组 | 下标表示边的编号, next[i]表示这个边的下一条边的编号 |
to数组 | 下标表示边的编号, to[i]表示这条边到达的点的编号 |
weight数组 | 下标表示边的编号, weight[i]表示这条边的权值 |
具体的例子
假设此时我们准备了五个点, 然后执行4次加边的操作
head数组长度准备为 6 (5 + 1) , next, to, weight 数组的长度都准备 5 (4 + 1)
下面执行加边的过程
[3,1,2], 这条边编号为1, 出发点是3, 终点是1, 权值是2, 在head中插入头边head[3] = 1(这里就是用的头插法, 这里是首条边), next[1] = 0 (头边所以是0) , to[1] = 1, weight[1] = 2
代码实现如下
public class day23 {public static void main(String[] args) {int[][] edges = {{1, 2, 5}, {5, 3, 1}, {1, 4, 4}, {2, 5, 2}};
// //测试一下使用邻接矩阵法建图
// GraphUseMatrix graphUseMatrix = new GraphUseMatrix();
// graphUseMatrix.build(5);
// graphUseMatrix.unDirectGraph(edges);
// graphUseMatrix.showGraph();// //测试一下邻接表法建图
// GraphUseList graphUseList = new GraphUseList();
// graphUseList.build(5);
// graphUseList.unDirectGraph(edges);
// graphUseList.showGraph();//测试一下链式前向星建图GraphUseLinkedStar graphUseLinkedStar = new GraphUseLinkedStar();graphUseLinkedStar.build(5);graphUseLinkedStar.unDirectGraph(edges);graphUseLinkedStar.showGraph();}
}/*** 链式前向星建图法(静态的建图法)*/
class GraphUseLinkedStar {//定义点最大值private static final int MAXN = 11;//定义边最大值private static final int MAXM = 22;//构建head数组(以点为准)private static final int[] head = new int[MAXN];//构建next数组(以边为准)private static final int[] next = new int[MAXM];//准备to数组(以边为主)private static final int[] to = new int[MAXM];//准备weight数组(以边为主)private static final int[] weights = new int[MAXM];//定义一下当前点的个数private static int curN = 0;//定义一个计数器用于给边编号private static int cnt = 0;//传入一个点数n用来初始化图结构public void build(int n){//更新计数器cnt = 1;//初始化当前节点个数curN = n;//清除head即可(这里不用重置to, weights, next)Arrays.fill(head, 1, n + 1, 0);}//添加边的方法(其实就是链表的头插法)private void addEdge(int from, int ton, int weight){next[cnt] = head[from];to[cnt] = ton;weights[cnt] = weight;head[from] = cnt++;}//构建一个有向带权图public void directGraph(int[][] edges){for(int[] edge : edges){addEdge(edge[0], edge[1], edge[2]);}}//构建一个无向带权图public void unDirectGraph(int[][] edges){for(int[] edge : edges){addEdge(edge[0],edge[1],edge[2]);addEdge(edge[1],edge[0],edge[2]);}}//展示图的方法(类似于链表的遍历)public void showGraph(){System.out.println("链式前向星建图展示");for(int i = 1; i <= curN; i++){System.out.print("点" + i + "(邻点/权值) : ");for(int ei = head[i]; ei != 0; ei = next[ei]){System.out.print("[" + to[ei] + "," + weights[ei] + "]");}System.out.println();}}
}
执行结果如下
拓扑排序
拓扑排序基础原理介绍
拓扑排序的存在条件是在一个有向且无环图中的排序, 拓扑排序在某种程度上反应的是一件事的执行的先后顺序, 请看下图
这张图中, 黑色的字符表示节点的名称, 蓝色的数字指的是该位置的入度是多少, 上面我们提到过, 拓扑排序的过程可以视为完成某一件事的先后顺序, 假设我们最终想要完成的任务是f, 那我们下面的字符的序列也就是完成最终事件的顺序
拓扑排序不是唯一的, 比如下面的图
在这张图中, 先完成a还是先完成b都是可以的, 所以产生的拓扑排序的情况就有两种, 这就说明拓扑排序的结果可能有多种, 只要满足每一个节点的前面所需要的节点都完成了就可以
拓扑排序的应用举例 :
比如在计算机编译程序的过程中, 编译一个程序需要另外的程序结果才能编译完成, 所以很自然的就会出现程序编译的先后顺序问题, 见下图, 左侧的表格是编译一个程序所需要的已经完成的编译结果, 右面是完成a的编译过程的图, 右下角的两串字符串都是完成的顺序, 这同样说明拓扑排序不是唯一的
拓扑排序要求一个图有向, 这个很好理解, 做事情的步骤肯定是有先后的顺序的, 而且要无环, 这个我们就拿上面的编译过程理解, 假如a的编译过程依赖b, b的编译过程依赖a, 那这不就是矛盾了吗, 两者互相依赖谁也完成不了
拓扑排序步骤解析
实现拓扑排序用的是零入度删除法, 需要用到队列(特殊状况下用小根堆), 核心就是删除0入度的节点和因为该节点所造成的入度影响
这就解释了上面的图为什么我们要进行入度的标记, 首先图解一下0入度删除法的步骤
最终组合出来的删除节点的步骤就是最终的拓扑排序的答案
拓扑排序模板leetcode-课程表
leetcode210-课程表题目链接
class Solution {//我们采用邻接表建图就已经够用了private static ArrayList<ArrayList<Integer>> list = new ArrayList<>();//课的最大数目private static final int MAXN = 2001;//同时定义一个入读表private static final int[] indegree = new int[MAXN];//当前的数据个数private static int curN = 0;public int[] findOrder(int numCourses, int[][] prerequisites) {build(numCourses);directGraph(prerequisites);//定义一个计数器看一看队列弹出了多少次(建议使用数组形式队列)int cntNum = 0;Queue<Integer> queue = new ArrayDeque<>();//首先遍历入度数组加入入度为0的节点for (int i = 0; i < numCourses; i++) {if (indegree[i] == 0) {queue.offer(i);}}//从队列中逐渐弹出元素进行判断int[] res = new int[numCourses];while (!queue.isEmpty()) {int index = queue.poll();//在返回数组中添加上该元素res[cntNum++] = index;//遍历其所属的列表删除这个元素造成的入度for(int elem : list.get(index)){if(--indegree[elem] == 0){queue.offer(elem);}}}return cntNum == res.length ? res : new int[0];}//初始化图结构private static void build(int n) {//更新当前的数据个数curN = n;//更新顺序表list.clear();for (int i = 0; i < n; i++) {list.add(new ArrayList<>());}//更新入度表Arrays.fill(indegree, 0, n, 0);}//建图的主函数(本质是有向无权图), 建图的过程中同时完成入度的统计private static void directGraph(int[][] edges) {for (int[] edge : edges) {addEdge(edge[1], edge[0]);indegree[edge[0]]++;}}//插入边的函数private static void addEdge(int from, int to) {list.get(from).add(to);}
}
相关文章:

算法-图论(建图,拓扑排序)
文章目录 建图的三种方式邻接矩阵邻接表链式前向星 拓扑排序拓扑排序基础原理介绍拓扑排序步骤解析拓扑排序模板leetcode-课程表 建图的三种方式 我们建图的三种方式分别是邻接矩阵, 邻接矩阵, 链式前向星 邻接矩阵 假设我们的点的个数为N个, 我们就把他们的下标依次标为1, …...
天童教育:课外阅读图书推荐
新学期开始了,现在正是孩子培养良好的阅读习惯的关键时期。让孩子感受阅读,爱上阅读,无疑会丰富孩子的日常生活,开阔孩子的视野,帮助孩子更好地生活。今天西安天童教育就和大家推荐几本适合孩子看的课外阅读书目&#…...

“汉语新解” Prompt新高度,火爆的李继刚
“汉语新解” prompt 是由李继刚设计的一个用于启发人工智能模型进行创意性文本生成的指令模板。这个 prompt 的设计初衷是为了让AI能够以一种独特的方式解析和重新诠释常见的中文词汇,从而产生出具有深刻洞察力和幽默感的文本内容,仿佛是由鲁迅或林语堂…...

论文:AOP框架安全框架-系统架构师(六十六)
1详细论述安全架构设计中鉴别框架和访问控制框架设计内容,并论述鉴别框架和访问控制所面临的主要威胁,说明其危害。 解析: 鉴别框架有用户密码鉴别、生物特征鉴别和多因素鉴别。 用户密码鉴别可以采用验证登入的用户账号是否正确。 生物特…...

用Unity2D制作一个人物,实现移动、跳起、人物静止和动起来时的动画:下(人物动画)
上个博客我们做出了人物的动画机和人物移动跳跃,接下来我们要做出人物展现出来的动画了 我们接下来就要用到动画机了,双击我们的动画机,进入到这样的页面,我这是已经做好的页面,你们是没有这些箭头的 依次像我一样连接…...
Android 优雅封装Glide
文章目录 Android 优雅封装Glide核心思想定义策略接口定义图片选项实现Glide策略图片管理类使用 Android 优雅封装Glide 核心思想 使用策略模式实现不同图片加载框架的切换,使用建造者设计模式处理不同参数,最后通过 ImageLoader 进行管理。 定义策略…...

智能优化算法-粒子群优化算法(PSO)(附源码)
目录 1.内容介绍 2.部分代码 3.实验结果 4.内容获取 1.内容介绍 粒子群优化算法 (Particle Swarm Optimization, PSO) 是一种基于群体智能的元启发式优化算法,由Kennedy和Eberhart于1995年提出。PSO模拟了鸟群或鱼群的觅食行为,通过粒子之间的相互作用…...

vue系统获取授权平台授权码实现单点登录、注销功能
公司平台需要对接别的平台 实现单点登录 注销。简而言之,不需要在自己公司系统登录 统一在别的平台登录后获取到登录凭证(授权码) 在本公司系统实现免密登录的功能。 流程: 跳转授权页面和保存授权码的代码: hrefLog…...

Java之枚举
目录 枚举 引入 定义 代码示例 常用方法 代码示例 枚举的优缺点 枚举和反射 面试题 枚举 引入 枚举是在JDK1.5以后引入的。主要用途是:将一组常量组织起来,在这之前表示一组常量通常使用定义常量的方式: publicstaticintfinalRED1;…...

八、适配器模式
适配器模式(Adapter Pattern)是一种结构型设计模式,它允许不兼容的接口之间进行合作。适配器模式通过创建一个适配器类来转换一个接口的接口,使得原本由于接口不兼容无法一起工作的类可以一起工作。 主要组成部分: 目标…...
关于E-R图
一 什么是E-R图 E-R图(Entity-Relationship Diagram)是一种数据建模工具,用于描述数据库中实体之间的关系。它使用实体(Entity)、属性(Attribute)和关系(Relationship&#…...

DVWA通关教程
Brute Force Low 先进行一下代码审计 <?php // 检查是否通过GET请求传递了Login参数(注意:这里应该是username或类似的,但代码逻辑有误) if( isset( $_GET[ Login ] ) ) { // 从GET请求中获取用户名 $user $_GET[ us…...

网络学习-eNSP配置VRRP
虚拟路由冗余协议(Virtual Router Redundancy Protocol,简称VRRP) VRRP广泛应用在边缘网络中,是一种路由冗余协议,它的设计目标是支持特定情况下IP数据流量失败转移不会引起混乱,允许主机使用单路由器,以及即使在实际…...

Kafka【九】如何实现数据的幂等性操作
为了解决Kafka传输数据时,所产生的数据重复和乱序问题,Kafka引入了幂等性操作,所谓的幂等性,就是Producer同样的一条数据,无论向Kafka发送多少次,kafka都只会存储一条。注意,这里的同样的一条数…...
JavaScript知识点1
目录 1.JavaScript中常用的数组方法有哪些? 2.JavaScript的同源策略? 3.JavaScript中的 NaN 是什么? 4.JavaScript中的split、slice、splice函数区别? 1.JavaScript中常用的数组方法有哪些? 在 JavaScript 中&…...

51单片机个人学习笔记11(AT24C02-I2C总线)
前言 本篇文章属于STC89C52单片机(以下简称单片机)的学习笔记,来源于B站教学视频。下面是这位up主的视频链接。本文为个人学习笔记,只能做参考,细节方面建议观看视频,肯定受益匪浅。 [1-1] 课程简介_哔哩…...

创建Java项目,可实现main方法运行,实现对性能数据的处理
1、Android Studio无法执行Java类的main方法问题及解决方法 Android Studio无法执行Java类的main方法问题及解决方法_delegatedbuild-CSDN博客 D:\workspaces\performanceTools\.idea 文件夹下,gardle.xml ,添加依赖 <option name"delegatedBuild"…...

JavaWeb(后端)
MVC MVC 就是 Model View Controller 的缩写,属于一种软件架构设计模式一种思想,把我们的项目分为控制器(Controller)、模型(Model)、视图(view)三个部分,model就是处理…...

828华为云征文 | 华为云Flexusx实例,高效部署Servas书签管理工具的优选平台
前言 华为云Flexus X实例,Servas书签管理工具部署的优选平台!828节日特惠,让高效管理您的知识宝藏触手可及。Flexus X实例以其卓越的算力、灵活的资源配置和智能调优技术,为Servas提供了稳定、高效的运行环境。无论是快速访问、安…...
分治法和动态规划法
一、分治法(Divide and Conquer) 定义 分治法是一种将大问题分解成若干个小问题,递归地解决这些小问题,然后将这些小问题的解合并起来得到原问题的解的算法策略。(子问题之间相互独立) 基本步骤 1.分解…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...
Vue记事本应用实现教程
文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展:显示创建时间8. 功能扩展:记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...

【网络安全产品大调研系列】2. 体验漏洞扫描
前言 2023 年漏洞扫描服务市场规模预计为 3.06(十亿美元)。漏洞扫描服务市场行业预计将从 2024 年的 3.48(十亿美元)增长到 2032 年的 9.54(十亿美元)。预测期内漏洞扫描服务市场 CAGR(增长率&…...
Objective-C常用命名规范总结
【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名(Class Name)2.协议名(Protocol Name)3.方法名(Method Name)4.属性名(Property Name)5.局部变量/实例变量(Local / Instance Variables&…...
基于数字孪生的水厂可视化平台建设:架构与实践
分享大纲: 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年,数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段,基于数字孪生的水厂可视化平台的…...

Psychopy音频的使用
Psychopy音频的使用 本文主要解决以下问题: 指定音频引擎与设备;播放音频文件 本文所使用的环境: Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...

微信小程序云开发平台MySQL的连接方式
注:微信小程序云开发平台指的是腾讯云开发 先给结论:微信小程序云开发平台的MySQL,无法通过获取数据库连接信息的方式进行连接,连接只能通过云开发的SDK连接,具体要参考官方文档: 为什么? 因为…...

(转)什么是DockerCompose?它有什么作用?
一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器。 Compose文件是一个文本文件,通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...