【数据结构 | 图论】如何用链式前向星存图(保姆级教程,详细图解+完整代码)
一、概述
链式前向星是一种用于存储图的数据结构,特别适合于存储稀疏图,它可以有效地存储图的边和节点信息,以及边的权重。
它的主要思想是将每个节点的所有出边存储在一起,通过数组的方式连接(类似静态数组实现链表)。这种方法的优点是存储空间小,查询速度快,尤其适合于处理大规模的图数据,在一些笔试或者竞赛的场景中经常使用。
下面,我们用这张图来图解一下链式前向星的存储逻辑:
二、前置准备
注意看这里的设定,以及我加粗的提示。
-
head
数组:head[i]
存储的是节点i
的第一条边的编号。这样,我们可以通过head[i]
快速找到从节点i
出发的所有边。 -
next
数组:next[j]
存储的是编号为j
的边的下一条边的编号。这样,我们可以通过next[j]
快速找到从同一个节点出发的下一条边。 -
to
数组:to[j]
存储的是编号为j
的边的终点节点编号。这样,我们可以通过to[j]
快速找到边j
的终点,也就是这条边要去往哪里。 -
weight
数组:weight[j]
存储的是编号为j
的边的权重。这样,我们可以通过weight[j]
快速找到边j的权重。 -
cnt
变量:cnt
用于存储边的数量,也表示边的编号。每添加一条边,cnt
就会增加1
。这样,我们可以通过cnt
快速知道当前图中边的数量,同时我们也认为cnt
是新添加边的编号。
三、初始化
public static void build(int n) {cnt = 1; // 边从1开始编号Arrays.fill(head, 1, n + 1, 0); // head[1 ... n] 全设为 0
}
在链式前向星中,我们使用cnt
来作为边的编号,由于边的编号是从1开始的,所以初始化时我们将cnt
设置为1。同时,将head
数组的所有元素设置为0
。因为head[i]
存储的是节点i
的第一条边的编号,所以,如果节点i
没有出度(即没有从节点i
出发的边),那么head[i]
就应该为0
。初始化时所有节点都没有出度,后续在添加边的时候,会更新对应的head[i]
的值。
四、添加边(重点)
在链式前向星中添加边的操作是最核心的,它涉及到head
、next
、to
、weight
数组的更新,以及边的编号cnt
的自增。
在看代码之前,我们先回顾一下各个结构的下标以及值的含义:
-
head
数组:下标i
表示节点编号,值head[i]
表示从节点i
出发的第一条边的编号。 -
next
数组:下标j
表示边的编号,值next[j]
表示编号为j
的边的下一条边的编号。 -
to
数组:下标j
表示边的编号,值to[j]
表示编号为j
的边的终点节点编号。 -
weight
数组:下标j
表示边的编号,值weight[j]
表示编号为j
的边的权重。
结合上述含义,我们来看代码就很清晰了:
// (u, v, w): 有一条边,从u节点指向v节点,权重为w
// 在每一次添加边时,cnt都表示当前未分配的边的编号,添加边后cnt需++
public static void addEdge(int u, int v, int w) {next[cnt] = head[u];to[cnt] = v;weight[cnt] = w;head[u] = cnt;++cnt;
}
首先,我们需要更新next
数组。next[cnt]
存储的是编号为cnt
的边的下一条边的编号。在添加新边时,我们将新边的next
置为旧的头边号head[u]
,这样就可以通过next[cnt]
快速找到从节点u
出发的下一条边。
然后,我们需要更新to
数组,将新边的终点设置为v
,这样就可以通过to[cnt]
快速找到边cnt
的终点。
更新weight
数组也很自然,就是将新边的权重设置为w
,最后,我们将节点u
的头边号修改为当前新边的编号,这样就可以通过head[u]
快速找到从节点u
出发的第一条边。
备注:记得每添加一条边,边的编号
cnt
就需要增加1
五、建图
建图分为有向图与无向图,输入的参数是一个二维数组edges
作为输入,这个数组的每个元素都是一个长度为3的数组,代表一条边的两个端点和这条边的权重。
// 建有向图
public static void directGraph(int[][] edges) {for (int[] edge : edges) {addEdge(edge[0], edge[1], edge[2]); // 添加有向边}
}// 建无向图
public static void undirectGraph(int[][] edges) {for (int[] edge : edges) {addEdge(edge[0], edge[1], edge[2]); // 添加边addEdge(edge[1], edge[0], edge[2]); // 添加反向边}
}
六、图解
下面这个数组提供了图的边信息,基本上题目都会给定形式的信息,让你去建图:
有一条边(u, v, w),表示从u节点指向v节点,权重为w
[[1, 6, 2],[1, 3, 57],[1, 4, 61],[2, 3, 100],[3, 5, 34],[4, 5, 13],
]
这里 u,v,w
的含义以及顺序应根据具体题目具体分析,这里的设定是(u, v, w)
表示一条边从u
节点指向v
节点,权重为w
。
// 添加边:
public static void addEdge(int u, int v, int w) {next[cnt] = head[u];to[cnt] = v;weight[cnt] = w;head[u] = cnt;++cnt;
}
下面我们图解一下,在链式前向星中,依次添加6条边到有向图中的逻辑。
如果看不懂,建议返回上面去看各个数组的下标以及值的含义。
添加边 {1, 6, 2}
head[1] = 1
:节点1
的第一条边的编号是1。next[1] = 0
:边1没有下一条边。to[1] = 2
:边1的终点是节点2。weight[1] = 6
:边1的权重是6。cnt
:2,表示当前边的数量是1,下一条边的编号是2。
添加边 {1, 3, 57}
head[1] = 2
:节点1
的第一条边的编号是2。next[2] = 1
:边2的下一条边是边1。to[2] = 3
:边2的终点是节点3。weight[2] = 57
:边2的权重是57。cnt
:3,表示当前边的数量是2,下一条边的编号是3。
添加边 {1, 4, 61}
head[1] = 3
:节点1
的第一条边的编号是3。next[3] = 2
:边3的下一条边是边2。to[3] = 4
:边3的终点是节点4。weight[3] = 61
:边3的权重是61。cnt
:4,表示当前边的数量是3,下一条边的编号是4。
添加边 {2, 3, 100}
head[2] = 4
:节点2
的第一条边的编号是4。next[4] = 0
:边4没有下一条边。to[4] = 3
:边4的终点是节点3。weight[4] = 100
:边4的权重是100。cnt
:5,表示当前边的数量是4,下一条边的编号是5。
添加边 {3, 5, 34}
head[3] = 5
:节点3
的第一条边的编号是5。next[5] = 0
:边5没有下一条边。to[5] = 5
:边5的终点是节点5。weight[5] = 34
:边5的权重是34。cnt
:6,表示当前边的数量是5,下一条边的编号是6。
添加边 {4, 5, 13}
head[4] = 6
:节点4
的第一条边的编号是6。next[6] = 0
:边6没有下一条边。to[6] = 5
:边6的终点是节点5。weight[6] = 13
:边6的权重是13。cnt
:7,表示当前边的数量是6,下一条边的编号是7。
七、遍历图
遍历图的逻辑也不难理解,就是对于每个节点,遍历其所有的邻居,根据next
数组不断去拿到和每个节点连接的边的编号,直到没有邻居节点为止,一步步跳着找嘛。
步骤如下:
- 对于每个节点,通过
head
数组找到该节点的第一条边。 - 通过
next
数组找到下一条边,直到next
数组的值为0
,表示没有更多的边。 - 在遍历过程中,可以通过
to
和weight
数组获取边的终点和权重。
我们用打印邻居节点的方式来验证遍历的结果:
public static void traversal(int n) {StringBuilder sb = new StringBuilder();sb.append("链式前向星遍历,u: (v, w)表示u有一条边前往v,权重为w\n");for (int i = 1; i <= n; i++) {sb.append("[").append(i).append("]: ");for (int ei = head[i]; ei > 0; ei = next[ei]) {sb.append("(").append(to[ei]).append(",").append(weight[ei]).append(") "); // 输出边的终点和权重}sb.append("\n");}System.out.println(sb.toString()); // 打印结果
}
八、完整代码
package cn.zhengyiyi;import java.util.Arrays;public class Main {public static int N = 11;public static int M = 21; /*** 编号为 i 的节点,其第一条边的编号为 head[i]* 备注:如果 head[i] 为0,说明没有一条边从节点 i 出发*/public static int[] head = new int[N];/*** 编号为 i 的边,它的下一条边是 next[i],*/public static int[] next = new int[M];/*** 编号为 i 的边,前往的节点是 to[i],也就是第 i 条边的终点是 to[i]*/public static int[] to = new int[M];/*** 编号为 i 的边,权重是 weight[i]*/public static int[] weight = new int[M];/*** 记录边的数量,初始时值为 1*/public static int cnt;// 初始化链式前向星public static void build(int n) {cnt = 1; // 边从1开始编号Arrays.fill(head, 1, n + 1, 0); // head[1 ... n] 全设为 0}// 添加一条边:(u->v,权重为w)public static void addEdge(int u, int v, int w) {// 1. 更新next数组,将新边的next置为旧的头边号head[u],方便后续跳到旧的头边号next[cnt] = head[u];// 2. 更新to数组,设置当前新边的终点为vto[cnt] = v; // 3. 更新weight数组,设置当前边的权重wweight[cnt] = w;// 4. 更新head数组,将原先的头边号修改为当前新边head[u] = cnt;// 5. 最后边的编号要自增++cnt;}// 建立有向图public static void directGraph(int[][] edges) {for (int[] edge : edges) {addEdge(edge[0], edge[1], edge[2]); // 添加有向边}}// 建立无向图public static void undirectGraph(int[][] edges) {for (int[] edge : edges) {addEdge(edge[0], edge[1], edge[2]); // 添加边addEdge(edge[1], edge[0], edge[2]); // 无向图需要添加反向边}}// 遍历图public static void traversal(int n) {StringBuilder sb = new StringBuilder();sb.append("链式前向星遍历,u: (v, w)表示u有一条边前往v,权重为w\n");for (int i = 1; i <= n; i++) {sb.append("[").append(i).append("]: ");for (int ei = head[i]; ei > 0; ei = next[ei]) {sb.append("(").append(to[ei]).append(",").append(weight[ei]).append(") "); // 输出边的终点和权重}sb.append("\n");}System.out.println(sb.toString()); // 打印结果}public static void main(String[] args) {int n = 5; // 节点数build(n); // 初始化int[][] directEdges = { // 有向图的边{ 1, 6, 2 },{ 1, 3, 57 },{ 1, 4, 61 },{ 2, 3, 100 },{ 3, 5, 34 },{ 4, 5, 13 }};directGraph(directEdges); // 建立有向图traversal(n); // 遍历有向图}
}
运行结果:
链式前向星遍历,u: (v, w)表示u有一条边前往v,权重为w
[1]: (4,61) (3,57) (6,2)
[2]: (3,100)
[3]: (5,34)
[4]: (5,13)
[5]:
相关文章:

【数据结构 | 图论】如何用链式前向星存图(保姆级教程,详细图解+完整代码)
一、概述 链式前向星是一种用于存储图的数据结构,特别适合于存储稀疏图,它可以有效地存储图的边和节点信息,以及边的权重。 它的主要思想是将每个节点的所有出边存储在一起,通过数组的方式连接(类似静态数组实现链表…...

气象预测新篇章:Python人工智能的变革力量
Python是功能强大、免费、开源,实现面向对象的编程语言,在数据处理、科学计算、数学建模、数据挖掘和数据可视化方面具备优异的性能,这些优势使得Python在气象、海洋、地理、气候、水文和生态等地学领域的科研和工程项目中得到广泛应用。可以…...

基于微信小程序的民宿短租系统设计与实现(论文+源码)_kaic
摘 要 随着社会的发展,出差、旅游成为常态,也就造成民宿短租市场的兴起。人们新到陌生的环境里找民宿一般都是通过中介。中介虽然可以快速找到合适的民宿但会收取大量的中介费用,这对刚到新环境里的人们来说是一笔大的资金支出。也有一些人通…...

vue3开发前端表单缓存自定义指令,移动端h5必备插件
开发背景 公司需要开发一款移动端应用,使用vue开发,用户录入表单需要本地缓存,刷新页面,或者不小心关掉重新进来,上次录入的信息还要存在。 这里有两种方案,第一种就是像博客平台一样,实时保存…...

骗子查询系统源码
源码简介 小权云黑管理系统 V1.0 功能如下: 1.添加骗子,查询骗子 2.可添加团队后台方便审核用 3.在线反馈留言系统 4.前台提交骗子,后台需要审核才能过 5.后台使用光年UI界面 6.新增导航列表,可给网站添加导航友链 7.可添加云黑类…...

目标检测+车道线识别+追踪
一种方法: 车道线检测-canny边缘检测-霍夫变换 一、什么是霍夫变换 霍夫变换(Hough Transform)是一种在图像处理和计算机视觉中广泛使用的特征检测技术,主要用于识别图像中的几何形状,尤其是直线、圆和椭圆等常见形状…...

非wpf应用程序项目【类库、用户控件库】中使用HandyControl
文章速览 前言参考文章实现方法1、添加HandyControl包;2、添加资源字典3、修改资源字典内容 坚持记录实属不易,希望友善多金的码友能够随手点一个赞。 共同创建氛围更加良好的开发者社区! 谢谢~ 前言 wpf应用程序中,在入口项目中…...

【python】flask执行上下文context,请求上下文和应用上下文原理解析
✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,…...

DDos系列攻击原理与防御原理
七层防御体系 静态过滤 命中黑名单 对确定是攻击的流量直接加入黑名单(源地址命中黑名单直接丢弃,缺乏机动性和扩展性) 畸形报文过滤 畸形报文攻击 TCP包含多个标记位,排列组合有规律 • 现象:TCP标记位全为1 …...

Python拆分PDF、Python合并PDF
WPS能拆分合并,但却是要输入编辑密码,我没有。故写了个脚本来做拆分,顺便附上合并的代码。 代码如下(extract.py) #!/usr/bin/env python """PDF拆分脚本(需要Python3.10)Usage::$ python extract.py <pdf-fil…...
SqlServer(4)经典总结大全-技巧总结-数据开发-基本函数-常识整理-经典面试题
六、技巧 1、11,12的使用,在SQL语句组合时用的较多 “where 11” 是表示选择全部 “where 12”全部不选, 如: if strWhere !‘’ begin set strSQL ‘select count(*) as Total from [’ tblName ] where ’ strWhere …...

ArcGIS矢量裁剪矢量
一、利用相交工具 Arctoolbox工具一分析工具一叠加分析一相交...

pygame用chatgpt绘制3d沿x轴旋转的
import pygame from pygame.locals import * import sys import mathpygame.init()width, height 800, 600 screen pygame.display.set_mode((width, height))vertices [(0, 100, 0), (100, 200, 0), (300, 100, 0)]angle 0 rotation_speed 2 # 可根据需要调整旋转速度 c…...
golang大小写规则的影响
目录 golang大小写的规则: 1、可见性(visibility): 2、包的导入和调用: 3、json序列化和反序列化: 4、结构体字段的导出和可见性: 5、方法和函数的导出和可见性 : 6、常量和变…...

基于Java在线考试系统系统设计与实现(源码+部署文档)
博主介绍: ✌至今服务客户已经1000、专注于Java技术领域、项目定制、技术答疑、开发工具、毕业项目实战 ✌ 🍅 文末获取源码联系 🍅 👇🏻 精彩专栏 推荐订阅 👇🏻 不然下次找不到 Java项目精品实…...
如何应对复杂软件工程的开发流程?
应对复杂软件工程的开发流程通常需要一个结构化和系统化的方法。这种方法不仅包括采用合适的技术和工具,还涉及到项目管理、团队协作、需求分析、设计、实施、测试、部署和维护等多个方面。以下是一些关键步骤,以及如何将这些步骤应用于使用LabVIEW进行软…...

JAVA的NIO和BIO底层原理分析
文章目录 一、操作系统底层IO原理1. 简介2. 操作系统进行IO的流程 二、BIO底层原理1. 什么是Socket2. JDK原生编程的BIO 三、Java原生编程的NIO1. 简介2. NIO和BIO的主要区别3. Reactor模式4. NIO的三大核心组件5. NIO核心源码分析 一、操作系统底层IO原理 1. 简介 IO&#x…...

Python学习从0到1 day18 Python可视化基础综合案例 1.折线图
我默记这段路的酸楚,等来年春暖花开之时再赏心阅读 —— 24.3.24 python基础综合案例 数据可视化 — 折线图可视化 一、折线图案例 1.json数据格式 2.pyecharts模块介绍 3.pyecharts快速入门 4.数据处理 5.创建折线图 1.json数据格式 1.什么是json 2.掌握如何使用js…...

HTML网站的概念
目录 前言: 1.什么是网页: 2.什么是网站: 示例: 3.服务器: 总结: 前言: HTML也称Hyper Text Markup Language,意思是超文本标记语言,同时HTML也是前端的基础&…...

【微服务】Nacos(配置中心)
文章目录 1.AP和CP1.基本介绍2.说明 2.Nacos配置中心实例1.架构图2.在Nacos Server加入配置1.配置列表,加号2.加入配置3.点击发布,然后返回4.还可以编辑 3. 创建 Nacos 配置客户端模块获取配置中心信息1.创建子模块 e-commerce-nacos-config-client50002…...

基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销,平衡网络负载,延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...

Netty从入门到进阶(二)
二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架,用于…...

【C++特殊工具与技术】优化内存分配(一):C++中的内存分配
目录 一、C 内存的基本概念 1.1 内存的物理与逻辑结构 1.2 C 程序的内存区域划分 二、栈内存分配 2.1 栈内存的特点 2.2 栈内存分配示例 三、堆内存分配 3.1 new和delete操作符 4.2 内存泄漏与悬空指针问题 4.3 new和delete的重载 四、智能指针…...
NPOI Excel用OLE对象的形式插入文件附件以及插入图片
static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...

打手机检测算法AI智能分析网关V4守护公共/工业/医疗等多场景安全应用
一、方案背景 在现代生产与生活场景中,如工厂高危作业区、医院手术室、公共场景等,人员违规打手机的行为潜藏着巨大风险。传统依靠人工巡查的监管方式,存在效率低、覆盖面不足、判断主观性强等问题,难以满足对人员打手机行为精…...
OD 算法题 B卷【正整数到Excel编号之间的转换】
文章目录 正整数到Excel编号之间的转换 正整数到Excel编号之间的转换 excel的列编号是这样的:a b c … z aa ab ac… az ba bb bc…yz za zb zc …zz aaa aab aac…; 分别代表以下的编号1 2 3 … 26 27 28 29… 52 53 54 55… 676 677 678 679 … 702 703 704 705;…...

Linux中《基础IO》详细介绍
目录 理解"文件"狭义理解广义理解文件操作的归类认知系统角度文件类别 回顾C文件接口打开文件写文件读文件稍作修改,实现简单cat命令 输出信息到显示器,你有哪些方法stdin & stdout & stderr打开文件的方式 系统⽂件I/O⼀种传递标志位…...
Monorepo架构: Nx Cloud 扩展能力与缓存加速
借助 Nx Cloud 实现项目协同与加速构建 1 ) 缓存工作原理分析 在了解了本地缓存和远程缓存之后,我们来探究缓存是如何工作的。以计算文件的哈希串为例,若后续运行任务时文件哈希串未变,系统会直接使用对应的输出和制品文件。 2 …...
node.js的初步学习
那什么是node.js呢? 和JavaScript又是什么关系呢? node.js 提供了 JavaScript的运行环境。当JavaScript作为后端开发语言来说, 需要在node.js的环境上进行当JavaScript作为前端开发语言来说,需要在浏览器的环境上进行 Node.js 可…...