考研数据结构之图(一)(包含真题及解析)
·
考研数据结构之图的存储与基本操作:邻接矩阵、邻接表、十字链表、邻接多重表
图(Graph)是数据结构中的重要非线性结构,广泛应用于网络路由、社交关系分析等领域。本文将详细讲解图的四种主要存储方式——邻接矩阵法、邻接表法、十字链表法和邻接多重表法,并结合基本操作进行解析。
一、图的基本概念
图由顶点集合和边集合组成,分为有向图和无向图。
- 顶点(Vertex):图中的节点,表示实体或对象。
- 边(Edge):连接两个顶点的路径,可以带权值。
在存储图时,需完整记录顶点和边的信息,同时兼顾空间效率和操作性能。
二、图的存储方式
1. 邻接矩阵法
(1)定义与实现
- 原理:用一个二维数组
G[V][V]表示图,其中G[i][j] = 1表示顶点i和j之间存在边;G[i][j] = 0表示不存在边。对于带权图,G[i][j]可存储权值。 - 特点:
- 优点:查询边是否存在的时间复杂度为
O(1),适合稠密图。 - 缺点:空间复杂度高,为
O(V^2),稀疏图浪费空间。
- 优点:查询边是否存在的时间复杂度为
(2)代码示例
#define MAX_V 100 // 最大顶点数
int G[MAX_V][MAX_V]; // 邻接矩阵
void InitGraph(int n) {
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
G[i][j] = 0; // 初始化为无边
}
2. 邻接表法
(1)定义与实现
- 原理:为每个顶点建立一个单链表,存储与其相邻的所有顶点。
- 特点:
- 优点:空间复杂度低,为
O(V + E),适合稀疏图。 - 缺点:查询边是否存在的时间复杂度为
O(deg(v)),其中deg(v)为顶点的度。
- 优点:空间复杂度低,为
(2)代码示例
typedef struct EdgeNode { // 边表节点
int adjvex; // 邻接点编号
int weight; // 边权值
struct EdgeNode *next;
} EdgeNode;
typedef struct VertexNode { // 顶点表节点
int data; // 顶点信息
EdgeNode *firstEdge; // 指向第一条边
} VertexNode;
VertexNode AdjList[MAX_V]; // 邻接表
void InitGraph(int n) {
for (int i = 0; i < n; i++) {
AdjList[i].data = i;
AdjList[i].firstEdge = NULL;
}
}
3. 十字链表法
(1)定义与实现
- 原理:结合邻接表和逆邻接表,记录每条边的起点和终点,支持快速查找入边和出边。
- 适用场景:主要用于有向图,尤其是需要频繁查询入边和出边的情况。
(2)代码示例
typedef struct ArcBox { // 弧节点
int tailvex, headvex; // 尾顶点和头顶点
struct ArcBox *hlink, *tlink; // 入边链和出边链
} ArcBox;
typedef struct VexNode { // 顶点节点
int data;
ArcBox *firstIn, *firstOut; // 第一条入边和出边
} VexNode;
VexNode CrossList[MAX_V]; // 十字链表
void InitGraph(int n) {
for (int i = 0; i < n; i++) {
CrossList[i].data = i;
CrossList[i].firstIn = NULL;
CrossList[i].firstOut = NULL;
}
}
4. 邻接多重表法
(1)定义与实现
- 原理:每条边仅存储一次,避免邻接表中重复存储无向图的边。
- 特点:适合无向图,节省空间且便于删除操作。
(2)代码示例
typedef struct EdgeNode { // 边节点
int ivex, jvex; // 边的两个顶点
struct EdgeNode *ilink, *jlink; // 连接到顶点的两条链
} EdgeNode;
typedef struct VertexNode { // 顶点节点
int data;
EdgeNode *firstEdge; // 第一条边
} VertexNode;
VertexNode AdjMultiList[MAX_V]; // 邻接多重表
void InitGraph(int n) {
for (int i = 0; i < n; i++) {
AdjMultiList[i].data = i;
AdjMultiList[i].firstEdge = NULL;
}
}
三、图的基本操作
1. 增加顶点与边
- 增加顶点:根据存储结构动态扩展顶点数组或链表。
- 增加边:
- 邻接矩阵:设置
G[i][j] = 1。 - 邻接表:创建新边节点并插入对应顶点的链表头部。
- 十字链表:更新弧节点的入边和出边链。
- 邻接多重表:插入边节点到两个顶点的链表中。
- 邻接矩阵:设置
2. 删除顶点与边
- 删除顶点:需同步删除所有与该顶点相关的边。
- 删除边:
- 邻接矩阵:设置
G[i][j] = 0。 - 邻接表:从链表中移除对应边节点。
- 十字链表:断开弧节点的入边和出边链。
- 邻接多重表:修改边节点的链接指针。
- 邻接矩阵:设置
3. 查找操作
- 查找边:
- 邻接矩阵:直接判断
G[i][j]是否为1。 - 邻接表:遍历链表查找目标边。
- 十字链表:通过入边链或出边链快速定位。
- 邻接多重表:通过顶点链表快速定位边。
- 邻接矩阵:直接判断
四、真题解析
1. 存储方式选择
题目(2022年真题):
对于一个包含1000个顶点的稀疏图,选择哪种存储方式更高效?
答案:
稀疏图中边的数量远小于V^2,因此邻接表法更高效,其空间复杂度为O(V + E),而邻接矩阵的空间复杂度为O(V^2)。
2. 图的操作实现
题目(经典真题):
实现一个函数,判断无向图中是否存在边
(u, v)。
解析:
- 邻接矩阵:返回
G[u][v] != 0。 - 邻接表:遍历顶点
u的链表,检查是否存在邻接点v。 - 邻接多重表:遍历顶点
u的边链表,检查是否存在边(u, v)。
五、总结
- 邻接矩阵法适合稠密图,查询效率高但空间开销大。
- 邻接表法适合稀疏图,节省空间但查询较慢。
- 十字链表法和邻接多重表法分别优化了有向图和无向图的存储与操作。
掌握这些存储方式及基本操作,可有效解决图相关的算法问题。后续文章将深入探讨图的遍历与最短路径算法。
更多推荐
所有评论(0)