图的存储和基本操作
图(Graph)是一种用于表示节点及其相互关系的数据结构,在计算机科学中有广泛的应用。图的存储和基本操作是理解和应用图数据结构的基础。以下总结了图的存储方式及其基本操作。
一、图的存储方式
-
邻接矩阵(Adjacency Matrix)
邻接矩阵是一种简单且直观的图的表示方法,用一个二维数组表示顶点之间的连接关系。
-
特点:
- 对于一个包含 (n) 个顶点的图,邻接矩阵是一个 (n \times n) 的二维数组。
- 若图中存在边 ( (i, j) ),则矩阵元素 ( A[i][j] = 1 )(或边的权值),否则 ( A[i][j] = 0 )。
- 对于无向图,邻接矩阵是对称的;对于有向图,矩阵不一定对称。
-
优缺点:
- 优点:简单直观,易于实现,方便查找两顶点之间是否存在边。
- 缺点:空间复杂度高,对于稀疏图不适用。
代码实现:
#define MAXVEX 100 typedef struct { int vexs[MAXVEX]; // 顶点表 int arc[MAXVEX][MAXVEX]; // 邻接矩阵 int numVertexes, numEdges; // 图中当前的顶点数和边数 } MGraph;
-
-
邻接表(Adjacency List)
邻接表是一种链表结构,每个顶点对应一个链表,链表中的节点表示与该顶点相连的其他顶点。
-
特点:
- 每个顶点都有一个链表,链表中的每个节点对应一个相邻顶点。
- 边表结点存储邻接点和指向下一个邻接点的指针。
-
优缺点:
- 优点:节省空间,适用于稀疏图。
- 缺点:查找顶点之间的边不如邻接矩阵方便。
代码实现:
typedef struct EdgeNode { // 边表结点 int adjvex; // 邻接点域,存储该顶点对应的下标 int weight; // 用于存储权值,对于非网图可以不需要 struct EdgeNode *next; // 链域,指向下一个邻接点 } EdgeNode; typedef struct VertexNode { // 顶点表结点 int data; // 顶点域,存储顶点信息 EdgeNode *firstEdge; // 边表头指针 } VertexNode, AdjList[MAXVEX]; typedef struct { AdjList adjList; int numVertexes, numEdges; // 图中当前顶点数和边数 } GraphAdjList;
-
-
十字链表和邻接多重表
为了更高效地处理稀疏图,有时会使用十字链表(Orthogonal List)和邻接多重表(Adjacency Multilist)等复杂的存储结构。
-
十字链表:
- 适用于有向图,将每条边用两个指针分别指向起点和终点顶点的链表。
-
邻接多重表:
- 适用于无向图,解决了邻接表中边的信息冗余问题,每条边只存储一次。
-
二、图的基本操作
-
插入操作:
- 在图中插入顶点和边。
邻接矩阵插入边:
void InsertEdge(MGraph *G, int i, int j) { if (i >= G->numVertexes || j >= G->numVertexes) return; G->arc[i][j] = 1; G->arc[j][i] = 1; // 无向图 G->numEdges++; }
邻接表插入边:
void InsertEdge(GraphAdjList *G, int i, int j) { EdgeNode *e = (EdgeNode *)malloc(sizeof(EdgeNode)); e->adjvex = j; e->next = G->adjList[i].firstEdge; G->adjList[i].firstEdge = e; e = (EdgeNode *)malloc(sizeof(EdgeNode)); e->adjvex = i; e->next = G->adjList[j].firstEdge; G->adjList[j].firstEdge = e; G->numEdges++; }
-
删除操作:
- 在图中删除顶点和边。
邻接矩阵删除边:
void DeleteEdge(MGraph *G, int i, int j) { if (i >= G->numVertexes || j >= G->numVertexes) return; G->arc[i][j] = 0; G->arc[j][i] = 0; // 无向图 G->numEdges--; }
邻接表删除边:
void DeleteEdge(GraphAdjList *G, int i, int j) { EdgeNode *p = G->adjList[i].firstEdge; EdgeNode *pre = NULL; while (p && p->adjvex != j) { pre = p; p = p->next; } if (p) { if (pre) pre->next = p->next; else G->adjList[i].firstEdge = p->next; free(p); } p = G->adjList[j].firstEdge; pre = NULL; while (p && p->adjvex != i) { pre = p; p = p->next; } if (p) { if (pre) pre->next = p->next; else G->adjList[j].firstEdge = p->next; free(p); } G->numEdges--; }
-
遍历操作:
- 遍历图的所有顶点和边。常用的遍历方法有深度优先搜索(DFS)和广度优先搜索(BFS)。
深度优先搜索(DFS):
void DFS(GraphAdjList *G, int i, int *visited) { visited[i] = 1; printf("%d ", G->adjList[i].data); EdgeNode *p = G->adjList[i].firstEdge; while (p) { if (!visited[p->adjvex]) { DFS(G, p->adjvex, visited); } p = p->next; } } void DFSTraverse(GraphAdjList *G) { int visited[MAXVEX]; for (int i = 0; i < G->numVertexes; i++) visited[i] = 0; for (int i = 0; i < G->numVertexes; i++) { if (!visited[i]) DFS(G, i, visited); } }
广度优先搜索(BFS):
void BFS(GraphAdjList *G, int i, int *visited) { int queue[MAXVEX], front = 0, rear = 0; visited[i] = 1; printf("%d ", G->adjList[i].data); queue[rear++] = i; while (front != rear) { int u = queue[front++]; EdgeNode *p = G->adjList[u].firstEdge; while (p) { if (!visited[p->adjvex]) { visited[p->adjvex] = 1; printf("%d ", G->adjList[p->adjvex].data); queue[rear++] = p->adjvex; } p = p->next; } } } void BFSTraverse(GraphAdjList *G) { int visited[MAXVEX]; for (int i = 0; i < G->numVertexes; i++) visited[i] = 0; for (int i = 0; i < G->numVertexes; i++) { if (!visited[i]) BFS(G, i, visited); } }
通过以上图的存储方式及其基本操作的理解和实现,可以更好地掌握图这一数据结构的应用和优化。在实际应用中,根据具体问题选择合适的存储方式和操作方法,能够有效提高算法的效率和性能。