考研数据结构之图(一)(包含真题及解析)

考研数据结构之图的存储与基本操作:邻接矩阵、邻接表、十字链表、邻接多重表

图(Graph)是数据结构中的重要非线性结构,广泛应用于网络路由、社交关系分析等领域。本文将详细讲解图的四种主要存储方式——邻接矩阵法邻接表法十字链表法邻接多重表法,并结合基本操作进行解析。


一、图的基本概念

图由顶点集合边集合组成,分为有向图和无向图。

  • 顶点(Vertex):图中的节点,表示实体或对象。
  • 边(Edge):连接两个顶点的路径,可以带权值。

在存储图时,需完整记录顶点和边的信息,同时兼顾空间效率和操作性能。


二、图的存储方式

1. 邻接矩阵法

(1)定义与实现
  • 原理:用一个二维数组G[V][V]表示图,其中G[i][j] = 1表示顶点ij之间存在边;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)

五、总结

  • 邻接矩阵法适合稠密图,查询效率高但空间开销大。
  • 邻接表法适合稀疏图,节省空间但查询较慢。
  • 十字链表法邻接多重表法分别优化了有向图和无向图的存储与操作。

掌握这些存储方式及基本操作,可有效解决图相关的算法问题。后续文章将深入探讨图的遍历与最短路径算法。

你可能感兴趣的:(数据结构与算法,考研,数据结构)