Jmeter压测实战:Shell脚本实现Linux服务器性能实时监控与自动化集成
2026/7/3 20:05:36
在计算机科学中,图是一种非常重要的非线性数据结构,它由顶点(Vertex)和边(Edge)组成,能够表示多对多的复杂关系。图的存储方式直接影响算法的效率和资源消耗,其中邻接矩阵和邻接表是最常用的两种存储结构。本文将用直观的对比方式,帮助读者快速理解这两种存储方式的本质区别。
图结构在现实世界中有广泛的应用场景,比如社交网络中的好友关系、交通网络中的路线规划、编译器中的控制流分析等。选择合适的存储结构需要考虑以下几个关键因素:
邻接矩阵和邻接表正是针对这些不同需求而设计的两种经典存储方案。下面我们通过具体例子来理解它们的实现原理。
邻接矩阵使用一个二维数组来表示图中顶点之间的连接关系。对于包含n个顶点的图,邻接矩阵是一个n×n的方阵。
#define MAX_VERTEX 100 typedef struct { int vertex[MAX_VERTEX]; // 顶点数组 int matrix[MAX_VERTEX][MAX_VERTEX]; // 邻接矩阵 int vertexNum, edgeNum; // 顶点数和边数 } AdjMatrixGraph;对于无权图,矩阵中的元素值为:
对于带权图(网),矩阵元素存储的是边的权值,通常用特殊值(如INT_MAX)表示无边。
优点:
缺点:
提示:邻接矩阵特别适合需要频繁判断顶点间是否存在边的场景,如路径查找算法。
邻接表采用数组+链表的方式存储图结构,每个顶点对应一个链表,存储其所有邻接顶点。
typedef struct EdgeNode { int adjvex; // 邻接顶点下标 int weight; // 边权重(可选) struct EdgeNode *next; // 指向下一个邻接点 } EdgeNode; typedef struct { char data; // 顶点数据 EdgeNode *firstEdge; // 边表头指针 } VertexNode; typedef struct { VertexNode adjList[MAX_VERTEX]; // 邻接表 int vertexNum, edgeNum; // 顶点数和边数 } AdjListGraph;优点:
缺点:
| 特性 | 邻接矩阵 | 邻接表 |
|---|---|---|
| 空间复杂度 | O(V²) | O(V+E) |
| 查询边存在 | O(1) | O(V) |
| 添加顶点 | O(V²) | O(1) |
| 添加边 | O(1) | O(1) |
| 删除边 | O(1) | O(V) |
| 遍历邻接点 | O(V) | O(degree(V)) |
| 适合图类型 | 稠密图 | 稀疏图 |
从表中可以看出,两种存储结构各有优劣,实际应用中需要根据具体场景选择:
在实际开发中,选择图的存储结构需要考虑以下几个因素:
// 邻接表添加边的示例代码 void addEdge(AdjListGraph *G, int v1, int v2, int weight) { EdgeNode *e = (EdgeNode*)malloc(sizeof(EdgeNode)); e->adjvex = v2; e->weight = weight; e->next = G->adjList[v1].firstEdge; G->adjList[v1].firstEdge = e; // 无向图需要添加反向边 e = (EdgeNode*)malloc(sizeof(EdgeNode)); e->adjvex = v1; e->weight = weight; e->next = G->adjList[v2].firstEdge; G->adjList[v2].firstEdge = e; }在项目实践中,我曾经遇到一个需要处理百万级顶点图的问题。最初使用邻接矩阵导致内存溢出,切换到邻接表后不仅解决了内存问题,还因为数据局部性更好而提升了缓存命中率,整体性能反而有所提升。