算法竞赛中的数据结构:图

作者:喜欢吃燃面日期:2025/12/27

目录

  • 一.图的基本概念
    • 1.图的定义
    • 2.图、树、线性表的联系与区别
      • 2.1 核心联系
        • 2.2 核心区别
  • 二.图的分类
    • 1.按边的方向分类
    • 2.按边的权重分类
    • 3 .按顶点和边的数量分类
    • 4 .按连通性分类(针对无向图)
    • 5 .按强连通性分类(针对有向图)
    • 6 .其他特殊类型
    • 7.顶点的度(补充)
    • 8.路径及相关长度概念(补充)
      • 8.1 路径
        • 8.2 路径长度(无权图)
        • 8.3 带权路径长度(带权图)
        • 8.4 核心区别对比
  • 三.邻接矩阵
    • 1.邻接矩阵
      • 【注意】
  • 四.邻接表
  • 五.链式前向星

Hello,小伙伴们!又到了咱们一起捣鼓代码的时间啦!💪 把生活调成热情模式,带着满满的能量钻进编程的奇妙世界吧——今天也要写出超酷的代码,冲鸭!🚀
在这里插入图片描述

我的博客主页:喜欢吃燃面
我的专栏:《C语言》《C语言之数据结构》《C++》《Linux学习笔记》
感谢你点开这篇博客呀!真心希望这些内容能给你带来实实在在的帮助~ 如果你有任何想法或疑问,非常欢迎一起交流探讨,咱们互相学习、共同进步,在编程路上结伴成长呀!

一.图的基本概念

在这里插入图片描述

1.图的定义

图(Graph) 是一种非线性的数据结构,由顶点集合(Vertex)边集合(Edge) 组成,用于表示多个对象之间的多对多关系。

其形式化定义为:
一个图 G GG 可以表示为二元组 G = ( V , E ) G=(V, E)G=(V,E)

  • V VV 是非空的顶点集合,其中的每个元素称为顶点(或节点),记为 v i v_ivi​;
  • E EE 是边的集合,其中的每个元素称为,记为 e j e_jej​,边用于连接 V VV 中的两个顶点。

2.图、树、线性表的联系与区别

三者都属于数据结构,核心是存储数据及数据间的关系,且线性表 ⊂ 树 ⊂ 图,是从简单到复杂、从一对一到多对多的关系扩展。

特性线性表
数据关系一对一(除首尾元素,每个元素仅有一个前驱和一个后继)一对多(每个节点有且仅有一个父节点,可多个子节点)多对多(任意两个顶点间可存在边)
结构约束有明确的先后顺序,是线性结构无环的连通结构,是层次结构可带环、可连通/非连通,无严格约束
典型代表数组、链表、栈、队列二叉树、红黑树、B树有向图、无向图、带权图(网)
核心特点结构简单,遍历顺序唯一(从头到尾)无环,遍历方式多(前/中/后序、层序)最灵活,可表示任意复杂关系

2.1 核心联系

  1. 包含关系
    • 线性表是最简单的结构,可以看作是特殊的树(树中每个节点只有一个子节点,退化为线性表)。
    • 树是特殊的图:树是无环的连通无向图,且满足 “n个顶点对应n-1条边” 的条件。
  2. 遍历逻辑相通
    三者的遍历都依赖迭代或递归,比如线性表的顺序遍历、树的深度优先遍历、图的深度优先遍历,核心思想都是“按规则访问每个元素一次”。

2.2 核心区别

  1. 关系复杂度不同
    • 线性表:一对一,像排队的人,每个人只和前后的人有关系。
    • 树:一对多,像家族族谱,一个父节点可以有多个子节点,但不能反过来。
    • 图:多对多,像城市交通网,任意两个城市之间可以有道路,还能有环路。
  2. 结构约束不同
    • 线性表必须是线性的、无分支
    • 树必须无环,且只有一个根节点
    • 图可以有环、有多个孤立顶点,没有根节点的概念。

二.图的分类

1.按边的方向分类

1.1 无向图

  • 边没有方向,连接顶点 u uu 和 v vv 的边记为 ( u , v ) (u,v)(u,v),且 ( u , v ) = ( v , u ) (u,v) = (v,u)(u,v)=(v,u)。
  • 示例:无向社交网络(A和B是朋友等价于B和A是朋友)、双向公路地图。

1.2 有向图

  • 边有方向,连接顶点 u uu 到 v vv 的边记为 < u , v > <u,v><u,v>, u uu 是起点, v vv 是终点,且 < u , v > ≠ < v , u > <u,v> \neq <v,u><u,v>=<v,u>。
  • 示例:有向社交关注(A关注B不等于B关注A)、单行道地图、任务依赖关系。

2.按边的权重分类

2.1 无权图

  • 边仅表示顶点间的连接关系,没有附加数值。
  • 示例:仅记录“是否相连”的拓扑图。

2.2 带权图(网)

  • 每条边都带有一个数值权重(如距离、成本、时间)。
  • 示例:带距离的城市交通图、带成本的通信网络。

3 .按顶点和边的数量分类

3.1 稀疏图

  • 边数远小于顶点数的平方( E ≪ V 2 E \ll V^2E≪V2),顶点间连接稀疏。
  • 示例:现实中的社交网络、路网。

3.2 稠密图

  • 边数接近顶点数的平方( E ≈ V 2 E \approx V^2E≈V2),顶点间连接紧密。
  • 示例:完全图、稠密的通信拓扑。

3.3 完全图

  • 无向完全图:任意两个顶点间都有一条边,边数为 V ( V − 1 ) 2 \frac{V(V-1)}{2}2V(V−1)​。
  • 有向完全图:任意两个顶点间都有两条方向相反的边,边数为 V ( V − 1 ) V(V-1)V(V−1)。

4 .按连通性分类(针对无向图)

4.1 连通图

  • 任意两个顶点之间都存在路径,整个图是一个连通分量。

4.2 非连通图

  • 存在至少两个顶点之间没有路径,图包含多个连通分量。

5 .按强连通性分类(针对有向图)

5.1 强连通图

  • 任意两个顶点 u uu 和 v vv 之间,既存在 u uu 到 v vv 的路径,也存在 v vv 到 u uu 的路径。

5.2 弱连通图

  • 将有向边改为无向边后是连通图,但本身不是强连通图。

5.3 非连通图

  • 将有向边改为无向边后仍是非连通图。

6 .其他特殊类型

6.1 无环图

  • 不包含环路的图,常见的如 有向无环图(DAG),用于表示任务调度、表达式求值等。

6.2 二分图

  • 顶点可分为两个不相交的集合,每条边的两个顶点分别属于不同集合,无同集合内的边。
  • 示例:用户-商品的购买关系图。

7.顶点的度(补充)

顶点的度是衡量顶点与其他顶点连接紧密程度的指标,在无向图和有向图中的定义不同。

  1. 无向图中的度
    一个顶点的度 = 与该顶点相连的边的数量,记为 d ( v ) d(v)d(v)。
    • 示例:无向图中顶点 A AA 连接了 3 条边,则 d ( A ) = 3 d(A)=3d(A)=3。
    • 性质:无向图所有顶点的度之和 = 2 × 2 \times2× 边数(每条边贡献 2 个度)。
  2. 有向图中的度
    有向图的度分为入度出度,两者之和为顶点的总度
    • 入度( d i n ( v ) d_{in}(v)din​(v)):以该顶点为终点的有向边数量。
    • 出度( d o u t ( v ) d_{out}(v)dout​(v)):以该顶点为起点的有向边数量。
    • 性质:有向图所有顶点的入度之和 = 所有顶点的出度之和 = 边数。

8.路径及相关长度概念(补充)

8.1 路径

路径是图中从一个顶点到另一个顶点的顶点序列,核心定义与属性如下:

  1. 定义
    给定图 G = ( V , E ) G=(V,E)G=(V,E),从顶点 v 0 v_0v0​ 到 v k v_kvk​ 的路径是一个顶点序列 v 0 , v 1 , v 2 , . . . , v k v_0,v_1,v_2,...,v_kv0​,v1​,v2​,...,vk​,满足任意相邻两个顶点 v i − 1 v_{i-1}vi−1​ 和 v i v_ivi​ 之间都有边相连。
  2. 关键属性
    • 路径长度:路径中包含的边的数量
    • 简单路径:路径中所有顶点不重复出现(无环路)。
    • 回路(环):起点和终点为同一个顶点的路径;若除起点终点外其他顶点不重复,则称为简单回路
  3. 示例
    • 无向图中顶点序列 A → B → C A \to B \to CA→B→C 是一条简单路径,长度为 2。
    • 序列 A → B → C → A A \to B \to C \to AA→B→C→A 是一条简单回路,长度为 3。

8.2 路径长度(无权图)

  1. 定义:在无权图中,路径长度指路径中包含的边的数量,与边的权重无关。
  2. 计算公式:若路径顶点序列为 v 0 → v 1 → v 2 → ⋯ → v k v_0 \to v_1 \to v_2 \to \dots \to v_kv0​→v1​→v2​→⋯→vk​,则路径长度 = k kk。
  3. 示例:无向图中路径 A → B → C A \to B \to CA→B→C 包含 2 条边,路径长度为 2;回路 A → B → C → A A \to B \to C \to AA→B→C→A 包含 3 条边,路径长度为 3

8.3 带权路径长度(带权图)

  1. 定义:在带权图(网)中,带权路径长度指路径中所有边的权重之和,是带权图的核心指标。
  2. 计算公式:若路径顶点序列为 v 0 → v 1 → v 2 → ⋯ → v k v_0 \to v_1 \to v_2 \to \dots \to v_kv0​→v1​→v2​→⋯→vk​,每条边的权重为 w ( v i − 1 , v i ) w(v_{i-1},v_i)w(vi−1​,vi​),则带权路径长度 = ∑ i = 1 k w ( v i − 1 , v i ) \sum_{i=1}^k w(v_{i-1},v_i)∑i=1k​w(vi−1​,vi​)。
  3. 示例:带权图中路径 A → B → C A \to B \to CA→B→C 的边权重分别为 w ( A , B ) = 3 w(A,B)=3w(A,B)=3、 w ( B , C ) = 5 w(B,C)=5w(B,C)=5,则带权路径长度 = 3 + 5 = 8 3+5=83+5=8。

8.4 核心区别对比

特性路径长度带权路径长度
适用图类型无权图带权图(网)
计算依据边的数量边的权重之和
典型应用路径步数统计最短路径算法(如 Dijkstra 算法)

三.邻接矩阵

1.邻接矩阵

邻接矩阵,指用一个矩阵(即二维数组)存储图中边的信息(即各个顶点之间的邻接关系),存储顶点之间邻接关系的矩阵称为邻接矩阵。

对于带权图而言,若顶点 v i v_ivi​ 和 v j v_jvj​ 之间有边相连,则邻接矩阵中对应项存放着该边对应的权值,若顶点 v i v_ivi​ 和 v j v_jvj​ 不相连,则用 ∞ \infty∞ 来代表这两个顶点之间不存在边。

对于不带权的图,可以创建一个二维的 bool 类型的数组,来标记顶点 v i v_ivi​ 和 v j v_jvj​ 之间有边相连。
在这里插入图片描述

在这里插入图片描述

【注意】

矩阵中元素个数为 n 2 n^2n2,即空间复杂度为 O ( n 2 ) O(n^2)O(n2), n nn 为顶点个数,和实际边的条数无关,适合存储稠密图。

1#include<iostream>
2#include<cstring>
3#include<queue>
4using namespace std;
5
6const int N = 110;
7
8// 邻接矩阵存储定义
9int edges[N][N];  // edges[a][b]表示a到b的边权(-1表示无边)
10int n, m;         // n:顶点数,m:边数
11bool st[N];       // 访问标记数组
12
13
14// 邻接矩阵版DFS
15void dfs_matrix(int u)
16{
17	cout << u << endl;
18	st[u] = true;
19	// 遍历所有邻接顶点(1~n)
20	for (int v = 1; v <= n; v++)
21	{
22		if (edges[u][v] != -1 && !st[v])
23			dfs_matrix(v);
24	}
25}
26
27
28// 邻接矩阵版BFS
29void bfs_matrix(int u)
30{
31	queue<int> q;
32	q.push(u);
33	st[u] = true;
34	while (q.size())
35	{
36		auto a = q.front();
37		q.pop();
38		cout << a << endl;
39		// 遍历所有邻接顶点(1~n)
40		for (int b = 1; b <= n; b++)
41		{
42			if (edges[a][b] != -1 && !st[b])
43			{
44				q.push(b);
45				st[b] = true;
46			}
47		}
48	}
49}
50
51
52// 邻接矩阵初始化
53int main()
54{
55	memset(edges, -1, sizeof(edges)); // 初始化:-1表示无边
56	cin >> n >> m;
57	// 输入边数据
58	for (int i = 1; i <= m; i++)
59	{
60		int a, b, value;
61		cin >> a >> b >> value;
62		edges[a][b] = value;
63		// 无向图需添加:edges[b][a] = value;
64	}
65
66	// 测试遍历
67	cout << "邻接矩阵DFS结果:" << endl;
68	dfs_matrix(1);
69	memset(st, false, sizeof(st)); // 重置标记
70
71	cout << "\n邻接矩阵BFS结果:" << endl;
72	bfs_matrix(1);
73	return 0;
74}
75

四.邻接表

不懂邻接表和链式前向星请查看:算法竞赛中的树

1#include<iostream>
2#include<cstring>
3#include<vector>
4#include<queue>
5using namespace std;
6
7const int N = 110;
8
9// 邻接表存储定义
10typedef pair<int, int> PII;       // <邻接顶点, 边权>
11vector<PII> edges_list[N];        // edges_list[a]存储a的所有邻接边
12int n, m;
13bool st[N];
14
15
16// 邻接表版DFS
17void dfs_list(int u)
18{
19	cout << u << endl;
20	st[u] = true;
21	// 遍历u的所有邻接边
22	for (auto &e : edges_list[u])
23	{
24		int v = e.first;
25		if (!st[v])
26			dfs_list(v);
27	}
28}
29
30
31// 邻接表版BFS
32void bfs_list(int u)
33{
34	queue<int> q;
35	q.push(u);
36	st[u] = true;
37	while (q.size())
38	{
39		auto a = q.front();
40		q.pop();
41		cout << a << endl;
42		// 遍历a的所有邻接边
43		for (auto &e : edges_list[a])
44		{
45			int b = e.first;
46			if (!st[b])
47			{
48				q.push(b);
49				st[b] = true;
50			}
51		}
52	}
53}
54
55
56// 邻接表初始化
57int main()
58{
59	cin >> n >> m;
60	// 输入边数据
61	for (int i = 1; i <= m; i++)
62	{
63		int a, b, value;
64		cin >> a >> b >> value;
65		edges_list[a].push_back({b, value});
66		// 无向图需添加:edges_list[b].push_back({a, value});
67	}
68
69	// 测试遍历
70	cout << "邻接表DFS结果:" << endl;
71	dfs_list(1);
72	memset(st, false, sizeof(st));
73
74	cout << "\n邻接表BFS结果:" << endl;
75	bfs_list(1);
76	return 0;
77}
78

五.链式前向星

1#include<iostream>
2#include<cstring>
3#include<queue>
4using namespace std;
5
6const int N = 110;
7
8// 链式前向星存储定义
9int h[N];       // h[a]:a的第一条边的编号
10int e[N * 2];   // e[id]:编号id的边的终点
11int ne[N * 2];  // ne[id]:编号id的边的下一条边
12int w[N * 2];   // w[id]:编号id的边的权值
13int id;         // 边的自增编号
14int n, m;
15bool st[N];
16
17
18// 链式前向星添加边
19void add(int a, int b, int value = 0)
20{
21	e[++id] = b;
22	w[id] = value;
23	ne[id] = h[a];
24	h[a] = id;
25}
26
27
28// 链式前向星版DFS
29void dfs_star(int u)
30{
31	cout << u << endl;
32	st[u] = true;
33	// 遍历u的所有边
34	for (int i = h[u]; i; i = ne[i])
35	{
36		int v = e[i];
37		if (!st[v])
38			dfs_star(v);
39	}
40}
41
42
43// 链式前向星版BFS
44void bfs_star(int u)
45{
46	queue<int> q;
47	q.push(u);
48	st[u] = true;
49	while (q.size())
50	{
51		auto a = q.front();
52		q.pop();
53		cout << a << endl;
54		// 遍历a的所有边
55		for (int i = h[a]; i; i = ne[i])
56		{
57			int b = e[i];
58			if (!st[b])
59			{
60				q.push(b);
61				st[b] = true;
62			}
63		}
64	}
65}
66
67
68// 链式前向星初始化
69int main()
70{
71	memset(h, 0, sizeof(h)); // 初始化头节点数组为0
72	cin >> n >> m;
73	// 输入边数据
74	for (int i = 1; i <= m; i++)
75	{
76		int a, b, value;
77		cin >> a >> b >> value;
78		add(a, b, value);
79		// 无向图需添加:add(b, a, value);
80	}
81
82	// 测试遍历
83	cout << "链式前向星DFS结果:" << endl;
84	dfs_star(1);
85	memset(st, false, sizeof(st));
86
87	cout << "\n链式前向星BFS结果:" << endl;
88	bfs_star(1);
89	return 0;
90}
91

算法竞赛中的数据结构:图》 是转载文章,点击查看原文


相关推荐


ZooKeeper+Kafka
吉良吉影1232025/12/18

目录 一、Zookeeper 1.1 Zookeeper 概述 1.2 Zookeeper 工作机制 1.3 ZooKeeper 特点 1.4 Zookeeper 数据结构 1.5 ZooKeeper 应用场景 1.6 Zookeeper 选举机制 1.6.1 第一次启动选举机制 1.6.2 非第一次启动选举机制 Leader 的作用 1. 处理所有写请求(核心职责) 2. 主导 Leader 选举 3. 管理集群数据同步 4. 维护集群状态 Follower


编程界 语言神 : 赶紧起来学 Rust 了!
Pomelo_刘金2025/12/10

大家对 Rust 的印象 没接触过的: 编程界语言神 整天重构这重构那 还要 要干掉 c++ ?! 稍微了解过的: 学习曲线: 但实际上是: 第一个高峰是 借用检查器,第二个是异步,第三个是unsafe,第四个是宏怎么玩? 开始接触之后 编译器不让我写代码,怎么写都报错 写 rust 代码像是在跟 rust 编译器谈对象 , 我只是传个参数,你跟我讲所有权、借用、生命周期?” 写的代码上线之后,还不错哦 “别的语言项目上线流程” 内容: 编译 ✔ 测试(偶尔挂一两条)✔ 上线后:半


单片机手搓掌上游戏机(十六)—pico运行fc模拟器之程序修改烧录
Bona Sun2025/11/30

我们来山寨picosystem,毕竟79刀,有些地方还是要简化修改的。 到: https://github.com/fhoedemakers/PicoSystem_InfoNes 下载zip或者git clone都可以。 解压缩,用vscode 打开文件夹   修改的地方:  首先是那个VSYNC,也就是8引脚的一个输入信号,我能买到的st7789上都没有这个引脚,看了一下代码 就是等待它的下降沿,也就知道该刷下一屏了。  其实没多大作用,我孤陋寡闻,还没见过屏幕撕裂,


Linux系统安全及应用(账号权限管理、登录控制、弱口令、端口扫描)
晚风吹人醒.2026/1/5

目录 1. 账号管理与权限控制         1.1 基本安全措施:                 1.1.1 账号管理和文件权限                 1.1.2 密码安全控制                 1.1.3历史命令和自动注销         1.2 用户切换与提权: 2. 系统引导与登录控制         2.1 开关机安全控制:                 2.1.1 GRUB                 2.1.2 限制更改GRUB


绘制K线第二章:背景网格绘制
佛系打工仔2026/1/13

绘制K线第二章:背景网格绘制 在第一章的基础上,我们简单修饰一下,补充一个背景九宫格的绘制功能。这个功能可以让K线图更加清晰易读,帮助用户快速定位价格和时间。 二、网格配置 确定网格的行数和列数 在绘制网格之前,我们需要确定: 几行:将高度分成几等份(对应价格轴) 几列:将宽度分成几等份(对应时间轴) 例如:4列5行,表示宽度分成4等份,高度分成5等份。 在Config中配置 为了灵活配置网格,我们在 KLineConfig 中添加了两个字段: data class KLineConfig(


Python 线程局部存储:threading.local() 完全指南
哈里谢顿2026/1/21

一句话总结: threading.local() 是 Python 标准库提供的「线程局部存储(Thread Local Storage, TLS)」方案,让同一段代码在不同线程里拥有各自独立的变量空间,从而避免加锁,也避免了层层传参的狼狈。 1. 为什么需要线程局部存储? 在多线程环境下,如果多个线程共享同一个全局变量,就必须: 加锁 → 代码变复杂、性能下降; 或者层层传参 → 代码臃肿、可维护性差。 有些场景只想让线程各自持有一份副本,互不干扰: Web 服务:每个请求线程绑定自


JSyncQueue——一个开箱即用的鸿蒙异步任务同步队列
江澎涌2026/1/31

零、JSyncQueue JSyncQueue 是一个开箱即用的鸿蒙异步任务同步队列。 项目地址:github.com/zincPower/J… 一、JSyncQueue 有什么作用 在鸿蒙应用开发中,有时需要让多个异步任务按顺序执行,例如状态的转换处理,如果不加控制,会因为执行顺序混乱而产生一些莫名其妙的问题。 所以 JSyncQueue 提供了一个简洁的解决方案: 保证顺序执行:所有任务严格按照入队顺序执行,即使任务内部有异步操作也能保证顺序 两种执行模式:支持 "立即执行" 和 "延时执


【Linux】进程信号(上半)
Lsir10110_2026/2/8

当我们想要强行终止掉前台进程的时候,只需要按下Ctrl+c即可,但是Ctrl+c是如何精准杀掉前台进程的? 一、信号概念 1.如何理解信号 假设点了一份外卖,外卖员到了楼下会给你发信息或者打电话,那么这通电话或者这个信息就是信号,也就是用来提醒你需要去做某事的一种通知手段。那么对照Linux系统,信号就是内核向进程发送的通知,提醒进程需要去完成某个任务。 2.信号的特点 对照外卖例子,当点完外卖,我们肯定不会一直在门口等着外卖员,而是先忙手中的事情,比如打游戏,那么这个过程就叫做异步


React 性能优化:图片懒加载
NEXT062026/2/17

引言 在现代 Web 应用开发中,首屏加载速度(FCP)和最大内容绘制(LCP)是衡量用户体验的核心指标。随着富媒体内容的普及,图片资源往往占据了页面带宽的大部分。如果一次性加载页面上的所有图片,不仅会阻塞关键渲染路径,导致页面长时间处于“白屏”或不可交互状态,还会浪费用户的流量带宽。 图片懒加载(Lazy Loading)作为一种经典的性能优化策略,其核心思想是“按需加载”:即只有当图片出现在浏览器可视区域(Viewport)或即将进入可视区域时,才触发网络请求进行加载。这一策略能显著减少首屏


Gemini 3.1 Pro 正式发布:一次低调更新,还是谷歌的关键反击?
IvanCodes2026/2/25

今天凌晨,谷歌发布了新一代模型——Gemini 3.1 Pro 没有大型发布会,没有提前预热,甚至连宣传节奏都显得克制。 很多人会把它看作 Gemini 3 的小版本升级,但从目前披露的测试数据和演示能力来看,这更像是一次结构性强化,而不是简单的参数迭代。 如果说 Gemini 3 是谷歌重新回到核心竞争区间的标志,那么 Gemini 3.1 Pro,则明显带着更强的实战优化意味。 它在几个关键方向上给出了非常明确的信号:谷歌不只是追赶者。 性能升级:从可用到强势竞争 这次升

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 XYZ博客