数据结构(c++版):邻接矩阵的实现
图的矩阵表示:用数字编织的关系网
图(Graph)是计算机科学中一种重要的数据结构,它像一张错综复杂的蜘蛛网,能够清晰地表示事物之间的关系。让我们通过一个C++实现的邻接矩阵图类,来探索这种数据结构的奥秘。
图的骨架:邻接矩阵
想象你有一张城市地图,每个交叉路口是一个节点,道路则是连接这些节点的边。邻接矩阵就像一张表格,记录着每个路口到其他路口是否有道路相连。
class Graph
{
private:int topNums;int** edges;
public:Graph(int topNums);~Graph();void addEdges(int u, int v, int w);void printGraph();
};这个Graph类就像是一个城市规划师的工具箱,topNums是城市中路口的数量,edges则是记录道路信息的表格。
建造城市:构造函数
构造函数就像是在一片空地上规划城市的基础设施:
Graph::Graph(int topNums)
{this->topNums = topNums;edges = new int* [topNums];for (int i = 0; i < topNums; i++){edges[i] = new int[topNums];for (int j = 0; j < topNums; j++){edges[i][j] = inf;}}
}这个过程就像:
- 决定城市要有多少个路口(
topNums) - 为每个路口准备一个记录本(
edges[i] = new int[topNums]) - 初始化时,假设所有路口之间都没有道路(
edges[i][j] = inf)
铺设道路:添加边
void Graph::addEdges(int v, int u, int w)
{edges[v][u] = w;
}这个方法就像是在两个路口之间修建一条道路。v是起点,u是终点,w是道路的长度或权重。如果是无向图,我们通常需要同时设置edges[u][v] = w,就像修建双向车道。
查看城市规划:打印图
void Graph::printGraph()
{for (int i = 0; i < topNums; i++){for (int j = 0; j < topNums; j++){cout << edges[i][j]<<" ";}cout << endl;}
}这个方法就像打印出一张城市道路连接表,让你一眼看清哪些路口之间有道路相连,以及道路的长度。
清理现场:析构函数
Graph::~Graph()
{for (int i = 0; i < topNums; i++){delete[]edges[i];}delete[] edges;
}当城市不再需要时,析构函数负责清理所有分配的内存,就像拆除不再使用的道路和路口。
构建一个示例图
int main()
{Graph g(6);g.addEdges(1, 3, 2);g.addEdges(1, 4, 5);g.addEdges(3, 2, 1);g.addEdges(2, 4, 1);g.addEdges(0, 3, 1);g.addEdges(4, 5, 1);g.printGraph();return 0;
}这段代码构建了一个有6个节点的图,并添加了6条边。就像在一个有6个路口的城市中修建了6条道路。
二维指针数组的基本概念:
二维指针数组本质上是一个"指针的指针"结构。它就像一栋办公楼:
- 整栋楼是一个一级指针数组(
int** edges) - 每一层楼是一个二级指针数组(
int* edges[i]) - 每个办公室存储着一个具体的整数值(
edges[i][j])
int** edges; // 声明一个二维指针数组内存分配过程详解
在构造函数中,我们为这个二维数组分配内存:
edges = new int* [topNums]; // 1. 分配第一维:创建指针数组
for (int i = 0; i < topNums; i++)
{edges[i] = new int[topNums]; // 2. 为每个指针分配第二维数组for (int j = 0; j < topNums; j++){edges[i][j] = inf; // 3. 初始化每个元素}
}这个分配过程可以分为三个步骤:
分配第一维(楼层建设):
new int* [topNums]创建了一个包含topNums个指针的数组- 就像建造一栋有
topNums层的大楼
分配第二维(每层办公室):
edges[i] = new int[topNums]为每一层创建包含topNums个整数的数组- 就像在每层楼建造
topNums个办公室
初始化元素(办公室准备):
edges[i][j] = inf将每个位置初始化为无穷大(表示初始时无边)- 就像给每个办公室贴上"空置"标签
内存布局可视化
假设topNums=3,内存布局如下:
edges (int**)
│
├── edges[0] (int*) → [inf, inf, inf]
├── edges[1] (int*) → [inf, inf, inf]
└── edges[2] (int*) → [inf, inf, inf]这种布局不是连续的二维数组,而是指针数组指向多个独立的一维数组。这与传统的栈上二维数组(如int edges[3][3])在内存布局上有本质区别。
邻接矩阵的优缺点
邻接矩阵就像一张完整的城市道路规划表:
优点:
- 查找两个节点是否相邻非常快速(O(1)时间复杂度)
- 适合表示稠密图(边很多的情况)
- 容易实现各种图算法
缺点:
- 空间复杂度高(O(n²))
- 对于稀疏图(边很少的情况)会浪费大量空间
比喻总结
想象你是一位城市规划师:
Graph类是你的城市规划办公室- 构造函数是你拿到一块空地开始规划
addEdges是你在两个区域之间修建道路printGraph是你绘制城市道路地图- 析构函数是城市废弃后的拆除工作
邻接矩阵就像一张巨大的Excel表格,每个单元格记录着两个区域之间的连接情况。虽然对于大型稀疏城市(图)来说有点浪费纸张(内存),但对于小型或高度连接的城市,它能提供一目了然的全貌。
通过这个比喻,我们可以更直观地理解图的邻接矩阵表示法及其实现方式。无论是社交网络、交通系统还是神经网络,图结构都在帮助我们理解和建模复杂的关系网络。


