记录一下Compressed Sparse Row(CSR)压缩邻接表实现
无向图邻接表的压缩存储实现
无向图中每条边在普通邻接表存储时会被记录两次(A→B和B→A)。通过压缩存储,我们可以减少存储空间并提高效率。下面介绍一种称为CSR(Compressed Sparse Row)的压缩邻接表实现。
基本思路
CSR格式使用三个数组来表示图:
- edges:存储所有边的目标顶点
- indices:存储每个顶点的边起始位置
- degrees:存储每个顶点的度(可选)
代码实现
下面是一个完整的C++实现:
#include <vector>
#include <iostream>
class CompressedGraph {
private:
int numVertices; // 顶点数量
std::vector<int> edges; // 存储所有边的目标节点
std::vector<int> indices; // 每个顶点的边起始索引
public:
// 构造函数
CompressedGraph(int n) : numVertices(n) {
indices.resize(n + 1, 0); // n+1是因为需要一个额外元素表示结束位置
}
// 从边列表构建压缩邻接表
void buildFromEdges(const std::vector<std::pair<int, int>>& edgeList) {
// 计算每个顶点的度
std::vector<int> degrees(numVertices, 0);
for (const auto& edge : edgeList) {
degrees[edge.first]++;
if (edge.first != edge.second) // 排除自环
degrees[edge.second]++;
}
// 计算索引数组
indices[0] = 0;
for (int i = 0; i < numVertices; i++) {
indices[i + 1] = indices[i] + degrees[i];
}
// 重置度数组用于后续处理
std::vector<int> currentDegrees(numVertices, 0);
// 分配边数组空间
edges.resize(indices[numVertices], 0);
// 填充边数组
for (const auto& edge : edgeList) {
int u = edge.first;
int v = edge.second;
// 添加边 u -> v
edges[indices[u] + currentDegrees[u]++] = v;
// 添加边 v -> u (如果不是自环)
if (u != v) {
edges[indices[v] + currentDegrees[v]++] = u;
}
}
}
// 检查两个顶点之间是否有边
bool hasEdge(int u, int v) const {
for (int i = indices[u]; i < indices[u + 1]; i++) {
if (edges[i] == v) return true;
}
return false;
}
// 获取顶点的所有邻居
std::vector<int> getNeighbors(int vertex) const {
std::vector<int> neighbors;
for (int i = indices[vertex]; i < indices[vertex + 1]; i++) {
neighbors.push_back(edges[i]);
}
return neighbors;
}
// 打印图的结构
void print() const {
for (int i = 0; i < numVertices; i++) {
std::cout << "顶点 " << i << " 的邻居: ";
for (int j = indices[i]; j < indices[i + 1]; j++) {
std::cout << edges[j] << " ";
}
std::cout << std::endl;
}
}
// 获取图的边数
int getEdgeCount() const {
return edges.size() / 2; // 每条边存两次,所以除以2
}
};
使用示例
#include "CompressedGraph.h"
#include <iostream>
int main() {
// 创建一个有5个顶点的图
CompressedGraph graph(5);
// 定义边列表 (假设顶点编号从0开始)
std::vector<std::pair<int, int>> edges = {
{0, 1}, {0, 3}, {1, 2}, {1, 3}, {2, 4}, {3, 4}
};
// 构建图
graph.buildFromEdges(edges);
// 打印图结构
std::cout << "图的压缩邻接表表示:\n";
graph.print();
// 测试边查询
std::cout << "\n顶点0和1之间是否有边: " << (graph.hasEdge(0, 1) ? "是" : "否") << std::endl;
std::cout << "顶点0和2之间是否有边: " << (graph.hasEdge(0, 2) ? "是" : "否") << std::endl;
// 获取顶点的邻居
std::cout << "\n顶点1的所有邻居: ";
auto neighbors = graph.getNeighbors(1);
for (int neighbor : neighbors) {
std::cout << neighbor << " ";
}
std::cout << std::endl;
// 显示边数
std::cout << "\n图中边的数量: " << graph.getEdgeCount() << std::endl;
return 0;
}
工作原理说明
-
空间优化:虽然我们仍然存储每条边两次,但数据结构更紧凑,没有链表节点的开销
-
indices数组:提供了O(1)时间复杂度访问一个顶点的邻居列表
- indices[i]:表示顶点i的第一个邻居在edges数组中的位置
- indices[i+1]-1:表示顶点i的最后一个邻居在edges数组中的位置
-
图示例:
对于图:0 -- 1 -- 2 | | 3 -- 4压缩存储为:
- edges = [1,3, 0,2,3, 1,4, 0,1,4, 2,3]
- indices = [0, 2, 5, 7, 10, 12]
表示:
- 顶点0的邻居:edges[0]到edges[1],即1和3
- 顶点1的邻居:edges[2]到edges[4],即0,2,3
- 依此类推...
这种实现方式特别适用于大型稀疏图,提供了高效的存储和查询操作。

浙公网安备 33010602011771号