数据结构(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;
        }
    }
}

这个过程就像:

  1. 决定城市要有多少个路口(topNums
  2. 为每个路口准备一个记录本(edges[i] = new int[topNums]
  3. 初始化时,假设所有路口之间都没有道路(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. 初始化每个元素
    }
}

这个分配过程可以分为三个步骤:

  1. ​分配第一维(楼层建设)​​:

    • new int* [topNums] 创建了一个包含topNums个指针的数组
    • 就像建造一栋有topNums层的大楼
  2. ​分配第二维(每层办公室)​​:

    • edges[i] = new int[topNums] 为每一层创建包含topNums个整数的数组
    • 就像在每层楼建造topNums个办公室
  3. ​初始化元素(办公室准备)​​:

    • 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表格,每个单元格记录着两个区域之间的连接情况。虽然对于大型稀疏城市(图)来说有点浪费纸张(内存),但对于小型或高度连接的城市,它能提供一目了然的全貌。

通过这个比喻,我们可以更直观地理解图的邻接矩阵表示法及其实现方式。无论是社交网络、交通系统还是神经网络,图结构都在帮助我们理解和建模复杂的关系网络。

posted @ 2025-12-13 09:25  clnchanpin  阅读(52)  评论(0)    收藏  举报