一、引言:图的表示方式
在图论算法中,高效地存储图结构是解决问题的关键。两种最常用的存储方式是邻接矩阵和邻接表,它们各有优缺点,适用于不同的场景。本文将深入探讨这两种存储结构,帮助读者根据具体需求选择合适的表示方法。
二、邻接矩阵:直观的二维数组表示
1.基本概念
邻接矩阵是一个 V* V 的二维数组(V 为顶点数),其中:
-
矩阵元素
matrix[i][j] = 1表示顶点 i 和 j之间有边 -
matrix[i][j] = 0表示无边
2.代码实现
#include <iostream>
using namespace std;
int main() {
// 定义邻接矩阵(无向图)
int neighbor[7][7] = {
{0,0,0,0,0,0,0},
{0,0,1,1,0,0,0}, // 顶点1连接2和3
{0,1,0,1,0,1,0}, // 顶点2连接1、3和5
{0,1,1,0,1,1,1}, // 顶点3连接1、2、4、5、6
{0,0,0,1,0,0,0}, // 顶点4连接3
{0,0,1,1,0,0,1}, // 顶点5连接3和6
{0,0,0,1,0,1,0} // 顶点6连接3和5
};
// 计算顶点n的邻居数量
int n;
cin >> n;
int sum = 0;
for(int i = 1; i <= 6; i++) {
if(neighbor[n][i] == 1) {
sum++;
}
}
cout << "顶点" << n << "有" << sum << "个邻居";
return 0;
}
3.特点分析
-
无向图:矩阵关于主对角线对称(
matrix[i][j] == matrix[j][i]) -
有向图:非对称,
matrix[i][j]表示从 i到 j的边 -
空间复杂度:O(V^2)
-
查询效率:O(1) 判断任意两点是否相邻
4.优缺点
| 优点 | 缺点 |
|---|---|
| 直观易懂 | 空间占用大(O(V^2)) |
| 快速判断邻接关系 | 添加/删除顶点效率低 |
| 方便计算度数 | 不适合稀疏图(边数远小于V^2) |
三、邻接表:高效的动态存储
1.基本概念
邻接表为每个顶点维护一个链表(或动态数组),存储该顶点的所有邻居:
-
顶点 $i$ 的邻居列表包含所有与 $i$ 相邻的顶点
-
无向图中每条边存储两次
-
有向图中只存储出边或入边
2.代码实现
#include <iostream>
#include <vector>
using namespace std;
const int N = 100; // 最大顶点数
int main() {
vector<int> neighbor[N]; // 邻接表
// 构建无向图
neighbor[1].push_back(2); // 1-2
neighbor[2].push_back(1);
neighbor[1].push_back(3); // 1-3
neighbor[3].push_back(1);
neighbor[2].push_back(5); // 2-5
neighbor[5].push_back(2);
// 计算顶点x的邻居数量
int x;
cin >> x;
cout << "顶点" << x << "有" << neighbor[x].size() << "个邻居";
return 0;
}
3.DFS遍历实现
#include <iostream>
#include <vector>
using namespace std;
vector<int> neighbor[8]; // 邻接表
bool visited[8]; // 访问标记数组
void dfs(int current) {
cout << current << " "; // 访问当前顶点
visited[current] = true; // 标记已访问
// 递归访问所有未访问邻居
for(int i = 0; i < neighbor[current].size(); i++) {
int next = neighbor[current][i];
if(!visited[next]) {
dfs(next);
}
}
}
int main() {
// 构建图结构(代码同前)
// ...
// 从顶点0开始DFS遍历
dfs(0);
return 0;
}
4.特点分析
-
空间复杂度:O(V + E)(E 为边数)
-
查询效率:O((v)) 获取顶点 v 的邻居
-
动态性:高效添加/删除边
5.优缺点
| 优点 | 缺点 |
|---|---|
| 节省内存(稀疏图) | 判断两点是否相邻较慢 |
| 高效遍历邻接点 | 实现相对复杂 |
| 易于添加/删除边 | 存储有向图时需区分出/入边 |
四、’对比总结:如何选择存储结构
1.性能对比表
| 特性 | 邻接矩阵 | 邻接表 |
|---|---|---|
| 空间占用 | O(V^2) | O(V + E) |
| 检查邻接 | O(1) | O(\text{degree}(v)) |
| 遍历邻居 | O(V) | O(\text{degree}(v)) |
| 添加边 | O(1) | O(1) |
| 删除边 | O(1) | O(\text{degree}(v)) |
| 添加顶点 | O(V^2) | O(1) |
2.适用场景建议
-
邻接矩阵适用场景:
-
稠密图(边数接近 V^2)
-
需要频繁检查两点是否相邻
-
图规模较小
-
需要实现图论中的数学运算
-
-
邻接表适用场景:
-
稀疏图(边数远小于 V^2)
-
需要遍历所有邻接点
-
图规模较大
-
需要动态添加/删除顶点
-
五、实战应用:DFS遍历比较
1.邻接矩阵DFS实现
int visited[8] = {};
int villager[8][8] = {
{0,1,0,1,1,0,0,0},
{1,0,1,0,1,0,0,0},
{0,1,0,0,0,1,0,0},
{1,0,0,0,1,0,1,0},
{1,1,0,0,0,0,1,0},
{0,0,1,0,0,0,0,0},
{0,0,0,1,1,0,0,1},
{0,0,0,0,0,0,1,0}};
void dfs_matrix(int current) {
visited[current] = 1;
cout << current << " ";
for(int j = 0; j < 8; j++) {
if(villager[current][j] == 1 && visited[j] == 0)
dfs_matrix(j);
}
}
2.邻接表DFS实现
vector<int> neighbor[8];
bool visited[8];
void dfs_list(int current) {
cout << current << " ";
visited[current] = true;
for(int j = 0; j < neighbor[current].size(); j++) {
int next = neighbor[current][j];
if(!visited[next])
dfs_list(next);
}
}
3.性能分析
在稠密图中,邻接矩阵的DFS效率更高;在稀疏图中,邻接表的DFS更优:
-
邻接矩阵DFS:时间复杂度 O(V^2)
-
邻接表DFS:时间复杂度 O(V + E)
六、总结与扩展
-
邻接矩阵适合稠密图和小规模图
-
邻接表适合稀疏图和大规模图
-
实际应用中常根据具体需求选择合适结构
838

被折叠的 条评论
为什么被折叠?



