数据结构——图

article/2025/7/29 12:52:15

一、概念

由顶点的非空有限集合 V(由 n>0 个顶点组成)与边的集合 EEE(顶点之间的关系)构成的结构。其形式化定义为 G=(V,E)。

  • 顶点(Vertex):图中的数据元素通常称为顶点,在下面的示意图中我们使用圆圈来表示顶点。
  • 边(Edge):图中两个数据元素之间的关联关系通常称为边,在下面的示意图中我们使用连接两个顶点之间的线段来表示边。边的形式化定义为:e = <u,v>,表示从 u 到 v 的一条边,其中 u 称为起始点,v 称为终止点

  • 子图(Sub Graph):对于图 G=(V,E) 与 G′=(V′,E′ ),如果存在 V′⊆V ,E′⊆E,则称图 G′G^{'}G′ 是图 G`的一个子图。在下面的示意图中我们给出了一个图 G及其一个子图 G′。特别的,根据定义,G也是其自身的子图。

二、图的分类

1. 无向图和有向图

按照边是否有方向,我们可以将图分为两种类型:「无向图」和「有向图」。

  • 无向图(Undirected Graph):如果图中的每条边都没有指向性,则称为无向图。例如朋友关系图、路线图都是无向图。
  • 有向图(Directed Graph):如果图中的每条边都具有指向性,则称为有向图。例如流程图是有向图。

在无向图中,每条边都是由两个顶点组成的无序对。例如下图左侧中的顶点 v1 和顶点 v2之间的边记为 (v1,v2) 或 (v2,v1)。

在有向图中,有向边也被称为弧,每条弧是由两个顶点组成的有序对,例如下图右侧中从顶点 v1 到顶点 v2 的弧,记为 ⟨v1,v2⟩,v1被称为弧尾,v2被称为弧头,如下图所示

如果无向图中有 n 个顶点,则无向图中最多有 n×(n−1)/2n 条边。而具有 n×(n−1)/2n 条边的无向图称为 「完全无向图(Completed Undirected Graph)」

如果有向图中有 n个顶点,则有向图中最多有 n×(n−1)n 条弧。而具有 n×(n−1)n 条弧的有向图称为 「完全有向图(Completed Directed Graph)」

如下图所示,左侧为包含 444 个顶点的完全无向图,右侧为包含 444 个顶点的完全有向图

下面介绍一下无向图和有向图中一个重要概念 「顶点的度」

  • 顶点的度:与该顶点 vi相关联的边的条数,记为 TD(vi)。

例如上图左侧的完全无向图中,顶点 v3 的度为 3。

而对于有向图,我们可以将顶点的度分为 「顶点的出度」「顶点的入度」

  • 顶点的出度:以该顶点 vi为出发点的边的条数,记为 OD(vi)。
  • 顶点的入度:以该顶点 vi为终止点的边的条数,记为 ID(vi)。
  • 有向图中某顶点的度 = 该顶点的出度 + 该顶点的入度,即 TD(vi)=OD(vi)+ID(vi)

例如上图右侧的完全有向图中,顶点 v3 的出度为 3,入度为 3,顶点 v3 的度为 3+3=6

2. 环形图和无环图

3. 带权图

时,图不仅需要表示顶点之间是否存在某种关系,还需要表示这一关系的具体细节。这时候我们需要在边上带一些数据信息,这些数据信息被称为 。在具体应用中,权值可以具有某种具体意义,比如权值可以代表距离、时间以及价格等不同属性。

  • 带权图:如果图的每条边都被赋以⼀个权值,这种图称为带权图。
  • 网络:带权的连通⽆向图称为⽹络。

在下面的示意图中,我们给出了一个带权图的例子。

三、图的存储结构

图的结构比较复杂,我们需要表示顶点和边。一个图可能有任意多个(有限个)顶点,而且任何两个顶点之间都可能存在边。我们在实现图的存储时,重点需要关注边与顶点之间的关联关系,这是图的存储的关键。

图的存储可以通过「顺序存储结构」和「链式存储结构」来实现。其中顺序存储结构包括邻接矩阵和边集数组。链式存储结构包括邻接表、链式前向星、十字链表和邻接多重表。

1. 邻接矩阵

1.1. 概念

使用一个二维数组adjmatrix来存储顶点之间的邻接关系。

对于无权图来说,如果adjmatrix[i][j]为1,则说明顶点 vi到 vj存在边,如果adjmatrix[i][j]为0,则说明顶点 vi到 vj不存在边adjmatrix[i][j]为 w 并且 w≠∞则说明顶点 vi到 vj的权值为 w。如果adjmatrix[i][j] 为 ∞,则说明顶点 vi 到 vj不存在边。

对于带权图来说

  • 优点:实现简单,并且可以直接查询顶点 vi 与 vj之间是否有边存在,还可以直接查询边的权值。
  • 缺点:初始化效率和遍历效率较低,空间开销大,空间利用率低,并且不能存储重复边,也不便于增删节点。如果当顶点数目过大(比如当 n>10^5)时,使用邻接矩阵建立一个 n*n的二维数组不太现实
1.2. 代码实现
class Graph {// 构造函数,初始化图的顶点个数constructor(verCount) {this.verCount = verCount;// 使用二维数组表示邻接矩阵this.adjMatrix = new Array(verCount).fill(null).map(() => new Array(verCount).fill(null));}// 添加边addEdge(vi, vj, val) {if (!this.isValid(vi) || !this.isValid(vj)) {// 抛出错误,指示顶点无效throw new Error(`${vi} 或 ${vj} 不是有效的顶点。`);}// 在邻接矩阵中设置边的权值this.adjMatrix[vi][vj] = val;}// 获取边的权值getEdge(vi, vj) {if (!this.isValid(vi) || !this.isValid(vj)) {// 抛出错误,指示顶点无效throw new Error(`${vi} 或 ${vj} 不是有效的顶点。`);}// 返回邻接矩阵中的边权值return this.adjMatrix[vi][vj];}// 打印图的边printGraph() {for (let vi = 0; vi < this.verCount; vi++) {for (let vj = 0; vj < this.verCount; vj++) {const val = this.getEdge(vi, vj);if (val !== null && val !== undefined) {// 打印边的信息console.log(`${vi} - ${vj} : ${val}`);}}}}// 判断顶点是否有效isValid(v) {// 判断顶点是否在有效范围内return 0 <= v && v < this.verCount;}
}// 示例用法
const graph = new Graph(5);
const edges = [[1, 2, 5], [2, 1, 5], [1, 3, 30], [3, 1, 30], [2, 3, 14], [3, 2, 14], [2, 4, 26], [4, 2, 26]];// 添加边到图中
edges.forEach(([vi, vj, val]) => {graph.addEdge(vi, vj, val);
});try {// 尝试获取边的权值和打印图的边console.log(graph.getEdge(3, 4));graph.printGraph();
} catch (error) {// 捕获并打印错误信息console.error(error.message);
}

2. 边集数组

2.1. 概念

使用一个数组来存储存储顶点之间的邻接关系。数组中每个元素都包含一条边的起点 vi、终点 vj和边的权值 val(如果是带权图)

2.2. 代码实现
class EdgeNode {// 边信息类constructor(vi, vj, val) {this.vi = vi;  // 边的起点this.vj = vj;  // 边的终点this.val = val;  // 边的权值}
}class Graph {// 基本图类,采用边集数组表示constructor() {this.edges = [];  // 边数组}// 图的创建操作,edges 为边信息createGraph(edges = []) {edges.forEach(([vi, vj, val]) => {this.add_edge(vi, vj, val);});}// 向图的边数组中添加边:vi - vj,权值为 valadd_edge(vi, vj, val) {const edge = new EdgeNode(vi, vj, val);  // 创建边节点this.edges.push(edge);  // 将边节点添加到边数组中}// 获取 vi - vj 边的权值get_edge(vi, vj) {for (const edge of this.edges) {if (vi === edge.vi && vj === edge.vj) {return edge.val;}}return null;}// 根据边数组打印图printGraph() {this.edges.forEach(edge => {console.log(`${edge.vi} - ${edge.vj} : ${edge.val}`);});}
}// 示例用法
const graph = new Graph();
const edges = [[1, 2, 5], [1, 5, 6], [2, 4, 7], [4, 3, 9], [3, 1, 2], [5, 6, 8], [6, 4, 3]];// 添加边到图中
graph.createGraph(edges);// 获取边的权值和打印图的边
console.log(graph.get_edge(3, 4));
graph.printGraph();

3. 邻接表

3.1. 概念

使用顺序存储和链式存储相结合的存储结构来存储图的顶点和边。其数据结构包括两个部分,其中一个部分是数组,主要用来存放顶点的数据信息,另一个部分是链表,用来存放边信息。

在邻接表的存储方法中,对于对图中每个顶点 viv_ivi 建立一个线性链表,把所有邻接于 viv_ivi 的顶点链接到单链表上。这样对于具有 n 个顶点的图而言,其邻接表结构由 n 个线性链表组成。

然后我们在每个顶点前边设置一个表头节点,称之为「顶点节点」。每个顶点节点由「顶点域」和「指针域」组成。其中顶点域用于存放某个顶点的数据信息,指针域用于指出该顶点第 1 条边所对应的链节点。

为了方便随机访问任意顶点的链表,通常我们会使用一组顺序存储结构(数组)存储所有「顶点节点」部分,顺序存储结构(数组)的下标表示该顶点在图中的位置。

3.2. 代码实现
// 边信息类
class EdgeNode {// 构造函数,初始化边的终点、权值和下一条边constructor(vj, val) {this.vj = vj;    // 边的终点this.val = val;  // 边的权值this.next = null; // 下一条边}
}// 顶点信息类
class VertexNode {// 构造函数,初始化边的起点和下一个邻接点constructor(vi) {this.vi = vi;    // 边的起点this.head = null; // 下一个邻接点}
}// 图类,采用邻接表表示
class Graph {// 构造函数,初始化图的顶点个数和顶点数组constructor(verCount) {this.verCount = verCount;this.vertices = [];for (let vi = 0; vi < verCount; vi++) {const vertex = new VertexNode(vi);this.vertices.push(vertex);}}// 判断顶点 v 是否有效isValid(v) {return 0 <= v && v < this.verCount;}// 图的创建操作,edges 为边信息createGraph(edges = []) {edges.forEach(([vi, vj, val]) => {this.addEdge(vi, vj, val);});}// 向图的邻接表中添加边:vi - vj,权值为 valaddEdge(vi, vj, val) {if (!this.isValid(vi) || !this.isValid(vj)) {throw new Error(`${vi} 或 ${vj} 不是有效的顶点。`);}const vertex = this.vertices[vi];const edge = new EdgeNode(vj, val);edge.next = vertex.head;vertex.head = edge;}// 获取 vi - vj 边的权值getEdge(vi, vj) {if (!this.isValid(vi) || !this.isValid(vj)) {throw new Error(`${vi} 或 ${vj} 不是有效的顶点。`);}const vertex = this.vertices[vi];let curEdge = vertex.head;while (curEdge) {if (curEdge.vj === vj) {return curEdge.val;}curEdge = curEdge.next;}return null;}// 根据邻接表打印图的边printGraph() {for (const vertex of this.vertices) {let curEdge = vertex.head;while (curEdge) {console.log(`${vertex.vi} - ${curEdge.vj} : ${curEdge.val}`);curEdge = curEdge.next;}}}
}// 示例用法
const graph = new Graph(7);
const edges = [[1, 2, 5], [1, 5, 6], [2, 4, 7], [4, 3, 9], [3, 1, 2], [5, 6, 8], [6, 4, 3]];// 添加边到图中
graph.createGraph(edges);// 获取边的权值和打印图的边
console.log(graph.getEdge(3, 4));
graph.printGraph();

四、图论问题应用

图论和图论算法在计算机科学中扮演这很重要的角色,它提供了对很多问题都有效的一种简单而系统的建模方式。很多实际问题都可以转化为图论问题,然后使用图论的景点算法加以解决。例如:

  • 集成电路的设计和布线。
  • 互联网和路由移动电话网的路由设计。
  • 工程项目的计划安排问题。

常见的图论问题应用大概可以分为以下几类:图的遍历问题图的连通性问题图的生成树问题图的最短路径问题图的网络流问题二分图问题 等等


http://www.hkcw.cn/article/fgUbxkFBRQ.shtml

相关文章

【AI赋能,视界升级】智微智能S134 AI OPS,重构智慧大屏未来

智慧教室中&#xff0c;教师通过电子白板&#xff0c;4K高清课件、3D教学模型同步呈现&#xff0c;后排学生也能看清画面细节&#xff0c;课堂变得趣味十足&#xff1b;智能会议室里&#xff0c;会议内容、多人云会议多屏投放依旧畅通清晰&#xff0c;会议纪要自动生成Word/PPT…

ISCC-2025-web-wp

web 校赛 校赛靠着ENOCH师傅发力&#xff0c;也是一路躺进了区域赛&#xff0c;E师傅不好意思发这抽象比赛的wp(这比赛确实啥必到让人大开眼界&#xff0c;反正明年我是肯定不会打了)&#xff0c;我就顺手要过来连着区域赛的一起发了 web 150分 按照提示进入/includes/fla…

在CentOS7上使用tree查看目录树

文章目录 1. 利用yum安装tree2. 利用rpm安装tree2.1 下载tree的rpm包2.2 上传到云主机2.3 安装tree软件 3. 使用tree查看目录树4. 实战小结 1. 利用yum安装tree 执行命令&#xff1a;yum -y install tree CentOS7停止更新&#xff0c;即使更新镜像源&#xff0c;也无法正常安装…

【仿muduo库实现并发服务器】实现时间轮定时器

实现时间轮定时器 1.时间轮定时器原理2.项目中实现目的3.实现功能3.1构造定时任务类3.2构造时间轮定时器每秒钟往后移动添加定时任务刷新定时任务取消定时任务 4.完整代码 1.时间轮定时器原理 时间轮定时器的原理类似于时钟&#xff0c;比如现在12点&#xff0c;定一个3点的闹…

Webug4.0靶场通关笔记10- 第10关存储型XSS注入

目录 一、存储型XSS原理 二、代码审计 三、第10关 存储型XSS注入实战 1.打开靶场 2.渗透实战 本文通过《Webug4.0通关笔记系列》来进行Webug4.0靶场的渗透实战&#xff0c;本文讲解Webug4.0靶场第10关存储型XSS的渗透实战。 一、存储型XSS原理 存储型XSS&#xff08;Sto…

深入了解MCP基础与架构

一、引言 在人工智能技术以指数级速度渗透各行业领域的今天&#xff0c;我们正站在一个关键的技术拐点。当ChatGPT月活突破亿级、Gemini Pro实现多模态实时交互、Claude 3.5 Sonnet突破百万上下文长度&#xff0c;这些里程碑事件背后&#xff0c;一个崭新的大门逐步打开&#…

STM32F407VET6学习笔记9:编译输出固定大小.bin文件

今日学习如何输出固定大小的.bin编译文件 目录 Keil_V5 fromelf.exe 软件目录&#xff1a; 魔棒添加命令输出bin文件&#xff1a; 输出固定大小的bin文件&#xff1a; 计算bin文件大小&#xff1a; 安装 SRecord 工具集&#xff1a; 使用SRecord&#xff1a; 参考文章&#…

Spring Cloud 学习 —— 简单了解

Spring Cloud 简介 官方文档&#xff1a;https://docs.spring.io/spring-cloud-release/reference/index.html 在学习 Spring Cloud 之前&#xff0c;先了解一下什么是分布式系统&#xff1f; 分布式系统 分布式系统是由多个独立计算机&#xff08;节点&#xff09;通过网络…

FreeRTOS多任务系统①

多任务系统 回想一下我们以前在使用 51、STM32 单片机裸机(未使用实时操作系统)的时候一般都是在main 函数里面用 while(1)做一个大循环来完成所有的处理&#xff0c; 即应用程序是一个无限的循环&#xff0c; 循环中调用相应的函数完成所需的处理。 有时候我们也需要中断中完…

Celery简介

一、什么是异步任务队列 异步任务队列是指一种用于管理和调度异步执行任务的机制。具体来说&#xff0c;它允许将任务放入队列中&#xff0c;然后由后台进程异步处理这些任务&#xff0c;而不会阻塞主线程的执行。这种设计使得系统能够高效地处理耗时操作&#xff0c;同时保持…

【Livox雷达使用】

记录 目前livox雷达型号较多&#xff0c;适用范围广泛。后来出的雷达需要使用使用第二代SDK和驱动&#xff0c;如Mid360、HAP。之前在github上看有人问是否能一起安装&#xff0c;官方回答是可以的&#xff0c;我把livox SDK、livox_ros_driver和SDK2、driver2都下载了进行比较…

RS232转Profinet网关在检漏仪与西门子PLC里的应用

RS232转Profinet网关在检漏仪与西门子PLC里的应用 在工业自动化和控制领域&#xff0c;设备间的高效通信至关重要。RS232转Profinet网关作为一种关键的转换工具&#xff0c;能够将传统的RS232接口设备接入现代化的Profinet网络&#xff0c;从而实现数据的无缝传输和设备的远程…

公链地址生成曲线和算法

在区块链公链中&#xff0c;除了 ECDSA&#xff08;基于 secp256k1 曲线&#xff09; 和 EdDSA&#xff08;基于 Ed25519 曲线&#xff09; 之外&#xff0c;还有其他一些加密算法和椭圆曲线被用于生成公私钥对、签名验证或地址生成。这些算法和曲线的选择通常基于安全性、性能…

⭐ Unity AVProVideo插件自带播放器 脚本重构 实现视频激活重置功能

一、功能概述 本笔记记录直接修改插件自带的场景播放其中 原始的 MediaPlayerUI 脚本,实现激活时自动重置播放器的功能。 我用的插件版本是 AVPro Video - Ultra Edition 2.7.3 修改后的脚本将具备以下特性: 激活 GameObject 时自动重置播放位置到开头 可配置是否在重置后自…

C#命名类型前缀习惯改进

我这几天有一个疑惑&#xff0c;我之前用过一些变量命名&#xff0c;有些混乱&#xff0c;如string sql&#xff0c;string strSql&#xff0c;string sqlStr&#xff0c; string strName&#xff0c;string nameStr&#xff0c;bool boValid&#xff0c;stringbuilder sbFileN…

生成式AI如何重塑设计思维与品牌创新?从工具到认知革命的跃迁

当MidJourney生成的视觉方案出现在国际设计奖项的决赛名单&#xff0c;当Adobe Firefly成为设计师的标配工具&#xff0c;一个问题正从行业边缘走向中心&#xff1a;生成式人工智能&#xff08;GAI&#xff09;究竟在解构还是重构创意领域&#xff1f;作为深度参与AI与设计融合…

零知开源——STM32F407VET6驱动Flappy Bird游戏教程

简介 本教程使用STM32F407VET6零知增强板驱动3.5寸TFT触摸屏实现经典Flappy Bird游戏。通过触摸屏控制小鸟跳跃&#xff0c;躲避障碍物柱体&#xff0c;挑战最高分。项目涉及STM32底层驱动、图形库移植、触摸控制和游戏逻辑设计。 目录 简介 一、硬件准备 二、软件架构 三、…

超高频RFID读写器天线分类及应用场景

超高频RFID(Radio Frequency Identification,射频识别)技术作为一种先进的自动识别技术,已经在多个领域得到了广泛应用。作为RFID系统的重要组成部分,超高频RFID读写器天线不仅影响着系统的读取距离、读取速度和准确性,还决定了RFID系统的适应性和灵活性。本文将详细介绍…

第J2周:ResNet50V2算法实战与解析

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 batch_size32&#xff1a;每次训练取32张图像组成一个 batch img_size(224, 224)&#xff1a;图像输入大小匹配 ResNet50 的输入要求 epochs10&#xff1a;训练…

界面控件DevExpress WinForms中文教程:Banded Grid View - 如何固定Bands?

DevExpress WinForms拥有180组件和UI库&#xff0c;能为Windows Forms平台创建具有影响力的业务解决方案。DevExpress WinForms能完美构建流畅、美观且易于使用的应用程序&#xff0c;无论是Office风格的界面&#xff0c;还是分析处理大批量的业务数据&#xff0c;它都能轻松胜…