图的表示方法
在算法竞赛中,选择合适的图的表示方法对于算法的性能和代码的简洁性至关重要。本节介绍两种主要的图表示方法。
邻接矩阵表示法
【定义】邻接矩阵是一个二维数组,用于表示顶点之间的连接关系。如果顶点i和j之间有一条边,则matrix[i][j]等于这条边的权值(无权图则为1),否则为0或无穷大。
邻接矩阵的C++实现
const int MAXN = 1000; // 最大顶点数
int graph[MAXN][MAXN]; // 邻接矩阵
// 初始化图
void initGraph(int n) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (i == j) {
graph[i][j] = 0; // 自己到自己的距离为0
} else {
graph[i][j] = INT_MAX; // 初始化为无穷大,表示不可达
}
}
}
}
// 添加边(有向图)
void addEdge(int from, int to, int weight) {
graph[from][to] = weight;
}
// 添加边(无向图)
void addUndirectedEdge(int u, int v, int weight) {
graph[u][v] = weight;
graph[v][u] = weight; // 无向图需要添加两条边
}
邻接矩阵的优缺点
优点:
- 实现简单,容易理解
- 查询两点是否有边的时间复杂度为O(1)
- 适合稠密图(边数接近顶点数的平方)
- 方便实现Floyd等算法
缺点:
- 空间复杂度为O(V²),对于大型图会消耗大量内存
- 对于稀疏图(边数远小于顶点数的平方)效率低下
- 添加/删除顶点的操作复杂度高
邻接表表示法
【定义】邻接表使用一个顶点数组,每个顶点维护一个链表,存储与该顶点相连的所有顶点和边的信息。
邻接表的C++实现
使用STL的vector实现:
const int MAXN = 100000; // 最大顶点数
// 边的结构
struct Edge {
int to; // 目标顶点
int weight; // 权值
Edge(int _to, int _weight) : to(_to), weight(_weight) {}
};
vector<Edge> graph[MAXN]; // 邻接表
// 添加边(有向图)
void addEdge(int from, int to, int weight) {
graph[from].push_back(Edge(to, weight));
}
// 添加边(无向图)
void addUndirectedEdge(int u, int v, int weight) {
graph[u].push_back(Edge(v, weight));
graph[v].push_back(Edge(u, weight));
}
// 遍历从顶点u出发的所有边
void traverseEdges(int u) {
for (const Edge& e : graph[u]) {
cout << "Edge from " << u << " to " << e.to
<< " with weight " << e.weight << endl;
}
}
邻接表的优缺点
优点:
- 空间复杂度为O(V+E),适合稀疏图
- 快速查找顶点的所有邻居
- 添加边的操作简单高效
- 适合实现Dijkstra、Kruskal等算法
缺点:
- 查询两点之间是否有边需要O(degree)时间
- 实现相对复杂
- 不适合需要频繁查询两点连接关系的场景
其他表示方法
链式前向星
链式前向星是邻接表的一种静态实现,在某些竞赛中很常用:
const int MAXN = 10005; // 最大顶点数
const int MAXM = 100005; // 最大边数
struct Edge {
int to, weight, next;
} edges[MAXM];
int head[MAXN]; // 每个顶点的第一条边的索引
int cnt = 0; // 边的计数器
// 初始化
void init() {
cnt = 0;
memset(head, -1, sizeof(head));
}
// 添加边
void addEdge(int from, int to, int weight) {
edges[cnt].to = to;
edges[cnt].weight = weight;
edges[cnt].next = head[from];
head[from] = cnt++;
}
// 遍历从顶点u出发的所有边
void traverseEdges(int u) {
for (int i = head[u]; i != -1; i = edges[i].next) {
int v = edges[i].to;
int w = edges[i].weight;
// 处理边(u,v,w)
}
}
选择合适的表示方法
选择图的表示方法时,考虑以下因素:
- 图的稠密程度:稠密图选择邻接矩阵,稀疏图选择邻接表
- 需要的操作:频繁查询连接关系选择邻接矩阵
- 内存限制:内存受限时选择邻接表
- 算法需求:某些算法在特定表示下实现更容易
竞赛实战技巧
- 在ACM竞赛中,通常使用
vector<vector<int>>
或vector<vector<pair<int,int>>>
实现邻接表,代码简洁且灵活 - 对于静态图,链式前向星效率高,且容易实现
- 在处理大规模图时,注意内存限制,选择合适的表示方法
- 邻接矩阵适合Floyd算法,邻接表适合Dijkstra和BFS/DFS
练习题目推荐
- 洛谷P3916 图的遍历
- POJ 1236 Network of Schools
- CodeForces 20C Dijkstra?
通过这些题目练习不同表示方法的使用场景和实现技巧。