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

目录

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

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

一.图的基本概念

1.图的定义

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

其形式化定义为:

一个图 G G G 可以表示为二元组 G = ( V , E ) G=(V, E) G=(V,E)

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

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

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

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

2.1 核心联系

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

2.2 核心区别

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

二.图的分类

1.按边的方向分类

1.1 无向图

  • 边没有方向,连接顶点 u u u 和 v v v 的边记为 ( 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 u u 到 v v v 的边记为 < u , v > <u,v> <u,v>, u u u 是起点, v v v 是终点,且 < 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^2 E≪V2),顶点间连接稀疏。
  • 示例:现实中的社交网络、路网。

3.2 稠密图

  • 边数接近顶点数的平方( E ≈ V 2 E \approx V^2 E≈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 u u 和 v v v 之间,既存在 u u u 到 v v v 的路径,也存在 v v v 到 u u u 的路径。

5.2 弱连通图

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

5.3 非连通图

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

6 .其他特殊类型

6.1 无环图

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

6.2 二分图

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

7.顶点的度(补充)

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

  1. 无向图中的度
    一个顶点的度 = 与该顶点相连的边的数量 ,记为 d ( v ) d(v) d(v)。
    • 示例:无向图中顶点 A A A 连接了 3 条边,则 d ( A ) = 3 d(A)=3 d(A)=3。
    • 性质:无向图所有顶点的度之和 = 2 × 2 \times 2× 边数(每条边贡献 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_0 v0 到 v k v_k vk 的路径是一个顶点序列 v 0 , v 1 , v 2 , . . . , v k v_0,v_1,v_2,...,v_k v0,v1,v2,...,vk,满足任意相邻两个顶点 v i − 1 v_{i-1} vi−1 和 v i v_i vi 之间都有边相连。
  2. 关键属性
    • 路径长度 :路径中包含的边的数量
    • 简单路径 :路径中所有顶点不重复出现(无环路)。
    • 回路(环) :起点和终点为同一个顶点 的路径;若除起点终点外其他顶点不重复,则称为简单回路
  3. 示例
    • 无向图中顶点序列 A → B → C A \to B \to C A→B→C 是一条简单路径,长度为 2。
    • 序列 A → B → C → A A \to B \to C \to A A→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_k v0→v1→v2→⋯→vk,则路径长度 = k k k。
  3. 示例 :无向图中路径 A → B → C A \to B \to C A→B→C 包含 2 条边,路径长度为 2 ;回路 A → B → C → A A \to B \to C \to A A→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_k v0→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=1kw(vi−1,vi)。
  3. 示例 :带权图中路径 A → B → C A \to B \to C A→B→C 的边权重分别为 w ( A , B ) = 3 w(A,B)=3 w(A,B)=3、 w ( B , C ) = 5 w(B,C)=5 w(B,C)=5,则带权路径长度 = 3 + 5 = 8 3+5=8 3+5=8。

8.4 核心区别对比

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

三.邻接矩阵

1.邻接矩阵

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

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

对于不带权的图,可以创建一个二维的 bool 类型的数组,来标记顶点 v i v_i vi 和 v j v_j vj 之间有边相连。

【注意】

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

cpp 复制代码
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;

const int N = 110;

// 邻接矩阵存储定义
int edges[N][N];  // edges[a][b]表示a到b的边权(-1表示无边)
int n, m;         // n:顶点数,m:边数
bool st[N];       // 访问标记数组


// 邻接矩阵版DFS
void dfs_matrix(int u)
{
	cout << u << endl;
	st[u] = true;
	// 遍历所有邻接顶点(1~n)
	for (int v = 1; v <= n; v++)
	{
		if (edges[u][v] != -1 && !st[v])
			dfs_matrix(v);
	}
}


// 邻接矩阵版BFS
void bfs_matrix(int u)
{
	queue<int> q;
	q.push(u);
	st[u] = true;
	while (q.size())
	{
		auto a = q.front();
		q.pop();
		cout << a << endl;
		// 遍历所有邻接顶点(1~n)
		for (int b = 1; b <= n; b++)
		{
			if (edges[a][b] != -1 && !st[b])
			{
				q.push(b);
				st[b] = true;
			}
		}
	}
}


// 邻接矩阵初始化
int main()
{
	memset(edges, -1, sizeof(edges)); // 初始化:-1表示无边
	cin >> n >> m;
	// 输入边数据
	for (int i = 1; i <= m; i++)
	{
		int a, b, value;
		cin >> a >> b >> value;
		edges[a][b] = value;
		// 无向图需添加:edges[b][a] = value;
	}

	// 测试遍历
	cout << "邻接矩阵DFS结果:" << endl;
	dfs_matrix(1);
	memset(st, false, sizeof(st)); // 重置标记

	cout << "\n邻接矩阵BFS结果:" << endl;
	bfs_matrix(1);
	return 0;
}

四.邻接表

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

cpp 复制代码
#include<iostream>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;

const int N = 110;

// 邻接表存储定义
typedef pair<int, int> PII;       // <邻接顶点, 边权>
vector<PII> edges_list[N];        // edges_list[a]存储a的所有邻接边
int n, m;
bool st[N];


// 邻接表版DFS
void dfs_list(int u)
{
	cout << u << endl;
	st[u] = true;
	// 遍历u的所有邻接边
	for (auto &e : edges_list[u])
	{
		int v = e.first;
		if (!st[v])
			dfs_list(v);
	}
}


// 邻接表版BFS
void bfs_list(int u)
{
	queue<int> q;
	q.push(u);
	st[u] = true;
	while (q.size())
	{
		auto a = q.front();
		q.pop();
		cout << a << endl;
		// 遍历a的所有邻接边
		for (auto &e : edges_list[a])
		{
			int b = e.first;
			if (!st[b])
			{
				q.push(b);
				st[b] = true;
			}
		}
	}
}


// 邻接表初始化
int main()
{
	cin >> n >> m;
	// 输入边数据
	for (int i = 1; i <= m; i++)
	{
		int a, b, value;
		cin >> a >> b >> value;
		edges_list[a].push_back({b, value});
		// 无向图需添加:edges_list[b].push_back({a, value});
	}

	// 测试遍历
	cout << "邻接表DFS结果:" << endl;
	dfs_list(1);
	memset(st, false, sizeof(st));

	cout << "\n邻接表BFS结果:" << endl;
	bfs_list(1);
	return 0;
}

五.链式前向星

cpp 复制代码
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;

const int N = 110;

// 链式前向星存储定义
int h[N];       // h[a]:a的第一条边的编号
int e[N * 2];   // e[id]:编号id的边的终点
int ne[N * 2];  // ne[id]:编号id的边的下一条边
int w[N * 2];   // w[id]:编号id的边的权值
int id;         // 边的自增编号
int n, m;
bool st[N];


// 链式前向星添加边
void add(int a, int b, int value = 0)
{
	e[++id] = b;
	w[id] = value;
	ne[id] = h[a];
	h[a] = id;
}


// 链式前向星版DFS
void dfs_star(int u)
{
	cout << u << endl;
	st[u] = true;
	// 遍历u的所有边
	for (int i = h[u]; i; i = ne[i])
	{
		int v = e[i];
		if (!st[v])
			dfs_star(v);
	}
}


// 链式前向星版BFS
void bfs_star(int u)
{
	queue<int> q;
	q.push(u);
	st[u] = true;
	while (q.size())
	{
		auto a = q.front();
		q.pop();
		cout << a << endl;
		// 遍历a的所有边
		for (int i = h[a]; i; i = ne[i])
		{
			int b = e[i];
			if (!st[b])
			{
				q.push(b);
				st[b] = true;
			}
		}
	}
}


// 链式前向星初始化
int main()
{
	memset(h, 0, sizeof(h)); // 初始化头节点数组为0
	cin >> n >> m;
	// 输入边数据
	for (int i = 1; i <= m; i++)
	{
		int a, b, value;
		cin >> a >> b >> value;
		add(a, b, value);
		// 无向图需添加:add(b, a, value);
	}

	// 测试遍历
	cout << "链式前向星DFS结果:" << endl;
	dfs_star(1);
	memset(st, false, sizeof(st));

	cout << "\n链式前向星BFS结果:" << endl;
	bfs_star(1);
	return 0;
}
相关推荐
澜莲花2 小时前
python图色之opencv基础
开发语言·图像处理·python·opencv
盐焗西兰花2 小时前
鸿蒙学习实战之路-Core Vision Kit人脸检测实现指南
android·学习·harmonyos
黎雁·泠崖2 小时前
C 语言动态内存管理入门:malloc/calloc/realloc/free 核心函数详解
c语言·开发语言
iconball2 小时前
个人用云计算学习笔记 --26 OpenStack 核心服务
运维·笔记·学习·云计算·openstack
哈市雪花2 小时前
记录一次cmake无法正确使用vcpkg的问题
开发语言·c++
Yue丶越2 小时前
【C语言】文件操作
服务器·c语言·开发语言
HyperAI超神经2 小时前
【vLLM 学习】Profiling
人工智能·深度学习·学习·cpu·gpu·编程语言·vllm
三品吉他手会点灯2 小时前
STM32F103 学习笔记-21-串口通信(第1节)-串口通信协议简介
笔记·stm32·单片机·嵌入式硬件·学习
小李小李快乐不已2 小时前
贪心算法理论基础
c++·算法·leetcode·贪心算法