一、项目背景详细介绍

在图论与算法学习中,“图(Graph)”是一种极其重要的数据结构。它能表示对象之间的各种关系,例如社交网络中的“好友关系”、交通系统中的“路径连接”、计算机网络中的“路由连接”、编译器中的“依赖图”、AI 中的“状态转移图”等。

图有两种常见表示方式:

  1. 邻接矩阵(Adjacency Matrix):使用二维数组表示顶点间的边,适合稠密图;

  2. 邻接表(Adjacency List):每个顶点维护一条链表(或向量)保存其邻接点,适合稀疏图。

在稀疏图(即边数远小于 n2n^2n2 的场景)中,邻接表的存储效率远优于邻接矩阵。例如:

  • 社交网络中一个用户可能只与极少数其他人有联系;

  • 地图中一个城市只与少数邻近城市相连;

  • 项目依赖图中一个任务通常只依赖少量其他任务。

因此,在现代图算法(如 Dijkstra、BFS、DFS、Kruskal、Prim、拓扑排序等)的实现中,邻接表是最常用的基础数据结构。

本项目旨在用 现代 C++(C++17) 实现一个灵活、通用、可复用的邻接表类(支持有向图/无向图、带权图/无权图),并提供典型的图算法操作接口(如插入边、删除边、遍历、输出结构等)。
通过完整的教学实现,读者可以深入理解图的结构组织、存储方式、算法接口设计与工程化实现。


二、项目需求详细介绍

功能性需求:

  1. 实现一个 Graph 类(模板化或整型顶点):

    • 支持创建指定顶点数的图;

    • 支持 有向/无向图;

    • 支持 带权边;

    • 提供基础操作:

      • 添加边(addEdge(u, v, weight));

      • 删除边;

      • 输出图结构;

      • 获取某个顶点的邻接列表;

    • 提供 BFS(广度优先搜索) 与 DFS(深度优先搜索);

    • 支持检测图是否连通。

  2. 输出示例:

    • 创建一个图;

    • 添加若干边;

    • 输出邻接表;

    • 执行 DFS 与 BFS。


三、相关技术详细介绍

1. 图的基本概念

图由两部分组成:

  • 顶点(Vertex):表示对象;

  • 边(Edge):表示顶点间的关系,可带权值。

图可分为:

  • 有向图(Directed Graph):边具有方向 (u → v);

  • 无向图(Undirected Graph):边无方向 (u — v)。

2. 邻接表的结构

邻接表的核心思想是:

每个顶点都有一个“邻接链表”,存储与其相连的顶点及对应权值。

示例:


0: (1,5) (2,3)
1: (0,5) (3,2)
2: (0,3)
3: (1,2)

表示一个无向带权图:

  • 0 ↔ 1 权值 5;

  • 0 ↔ 2 权值 3;

  • 1 ↔ 3 权值 2。

3. 邻接表与邻接矩阵对比

特性邻接矩阵邻接表
存储复杂度O(n²)O(n + m)
适用场景稠密图稀疏图
查询某条边O(1)O(度(u))
枚举邻居O(n)O(度(u))
添加顶点较复杂较简单

4. C++ STL 设计

  • 使用 std::vector<std::vector<std::pair<int, int>>> adj;

  • 其中:

    • adj[u]:顶点 u 的邻接列表;

    • pair<int,int>:存储 (v, weight);

  • 对于无权图,权值可默认 1 或忽略。


四、实现思路详细介绍

  1. 类结构设计:


class Graph {
    int V; // 顶点数
    bool directed; // 是否为有向图
    std::vector<std::vector<std::pair<int,int>>> adj; // 邻接表
};

  1. 主要成员函数:

    • Graph(int V, bool directed=false):构造函数;

    • void addEdge(int u, int v, int w=1):添加边;

    • void removeEdge(int u, int v):删除边;

    • void printGraph():输出邻接表;

    • void BFS(int start):广度优先搜索;

    • void DFS(int start):深度优先搜索;

    • bool isConnected():判断图是否连通。

  2. 算法实现策略:

    • BFS 用 queue;

    • DFS 用递归;

    • visited 数组标记访问状态;

    • 输出顺序方便教学展示。


五、完整实现代码

/*************************************************************
 * 文件名: adjacency_list.cpp
 * 功能: 使用C++17实现邻接表图结构(支持有向/无向、带权图)
 * 编译:
 *   g++ -std=c++17 -O2 -Wall -o adjacency_list adjacency_list.cpp
 * 运行:
 *   ./adjacency_list
 *************************************************************/

#include <iostream>
#include <vector>
#include <queue>
#include <stack>
#include <utility>
#include <iomanip>

class Graph {
private:
    int V;                                  // 顶点数量
    bool directed;                          // 是否为有向图
    std::vector<std::vector<std::pair<int,int>>> adj; // 邻接表 (顶点, 权值)

public:
    // 构造函数
    Graph(int vertices, bool isDirected = false)
        : V(vertices), directed(isDirected), adj(vertices) {}

    // 添加边
    void addEdge(int u, int v, int w = 1) {
        if (u >= V || v >= V) {
            std::cerr << "错误: 顶点超出范围!" << std::endl;
            return;
        }
        adj[u].push_back({v, w});
        if (!directed) {
            adj[v].push_back({u, w});
        }
    }

    // 删除边
    void removeEdge(int u, int v) {
        if (u >= V || v >= V) return;
        auto &list = adj[u];
        list.erase(
            std::remove_if(list.begin(), list.end(),
                           [v](auto &p){ return p.first == v; }),
            list.end());
        if (!directed) {
            auto &list2 = adj[v];
            list2.erase(
                std::remove_if(list2.begin(), list2.end(),
                               [u](auto &p){ return p.first == u; }),
                list2.end());
        }
    }

    // 打印邻接表
    void printGraph() const {
        std::cout << "图的邻接表表示:" << std::endl;
        for (int i = 0; i < V; ++i) {
            std::cout << "顶点 " << i << " -> ";
            for (auto &[v, w] : adj[i]) {
                std::cout << "(" << v << ",权=" << w << ") ";
            }
            std::cout << std::endl;
        }
    }

    // BFS(广度优先搜索)
    void BFS(int start) const {
        if (start >= V) return;
        std::vector<bool> visited(V, false);
        std::queue<int> q;
        q.push(start);
        visited[start] = true;
        std::cout << "BFS遍历顺序: ";
        while (!q.empty()) {
            int u = q.front(); q.pop();
            std::cout << u << " ";
            for (auto &[v, w] : adj[u]) {
                if (!visited[v]) {
                    visited[v] = true;
                    q.push(v);
                }
            }
        }
        std::cout << std::endl;
    }

    // DFS(递归实现)
    void DFSUtil(int u, std::vector<bool> &visited) const {
        visited[u] = true;
        std::cout << u << " ";
        for (auto &[v, w] : adj[u]) {
            if (!visited[v]) {
                DFSUtil(v, visited);
            }
        }
    }

    void DFS(int start) const {
        if (start >= V) return;
        std::vector<bool> visited(V, false);
        std::cout << "DFS遍历顺序: ";
        DFSUtil(start, visited);
        std::cout << std::endl;
    }

    // 判断是否连通(无向图)
    bool isConnected() const {
        if (directed) {
            std::cerr << "警告: 连通性检测仅适用于无向图。" << std::endl;
            return false;
        }
        std::vector<bool> visited(V, false);
        DFSUtil(0, visited);
        for (bool v : visited) {
            if (!v) return false;
        }
        return true;
    }
};

/************** 主函数(测试示例) **************/
int main() {
    // 创建一个5个顶点的无向带权图
    Graph g(5, false);

    g.addEdge(0, 1, 2);
    g.addEdge(0, 4, 1);
    g.addEdge(1, 2, 3);
    g.addEdge(1, 3, 8);
    g.addEdge(1, 4, 5);
    g.addEdge(2, 3, 7);
    g.addEdge(3, 4, 4);

    std::cout << "===== 图结构演示 =====" << std::endl;
    g.printGraph();

    std::cout << "\n===== 遍历演示 =====" << std::endl;
    g.BFS(0);
    g.DFS(0);

    std::cout << "\n===== 连通性检测 =====" << std::endl;
    bool conn = g.isConnected();
    std::cout << (conn ? "图是连通的" : "图不是连通的") << std::endl;

    return 0;
}

六、代码详细解读

  • Graph(int vertices, bool isDirected):构造函数,初始化图的顶点数与方向类型;

  • addEdge(u, v, w):在 u 到 v 添加一条权值为 w 的边;若无向则反向添加;

  • removeEdge(u, v):删除两顶点间的连接;

  • printGraph():以文本形式输出邻接表;

  • BFS(start):层次遍历,使用 queue;

  • DFS(start):深度遍历,递归实现;

  • isConnected():在无向图上检测是否所有顶点可达。


七、项目详细总结

本项目从零实现了邻接表的存储结构与基本算法,完整展示了:

  • 图结构的设计与存储;

  • 邻接表与矩阵的比较;

  • BFS / DFS 的核心逻辑;

  • STL 在图算法中的应用;

  • 有向与无向的统一设计方法。

邻接表在现实应用中极为广泛,是理解复杂图算法(Dijkstra、Prim、Kruskal、拓扑排序)的基石。


八、项目常见问题及解答

Q1:为什么使用 vector<pair<int,int>> 而不是 list?
A1:因为大多数算法需要随机访问和排序,vector 更高效。

Q2:如何实现无权图?
A2:可在添加边时忽略 w 参数或设默认值 1。

Q3:是否可以支持字符顶点(如 A、B、C)?
A3:可以,将顶点映射为整数索引(如 A→0)。

Q4:BFS 和 DFS 有什么区别?
A4:BFS 层次遍历,优先访问距离近的顶点;DFS 深度优先,沿一条路径走到底再回溯。

Q5:为什么 isConnected 不适用于有向图?
A5:因为有向图的连通性需要判断“强连通分量”,需要 Tarjan/Kosaraju 算法。


九、扩展方向与性能优化

  1. 带权最短路径:在邻接表上实现 Dijkstra、Bellman-Ford;

  2. 最小生成树:实现 Prim 或 Kruskal;

  3. 拓扑排序:针对有向无环图(DAG);

  4. 强连通分量检测:基于 Tarjan 算法;

  5. 图可视化导出:将邻接表输出为 Graphviz .dot 文件;

  6. 模板化设计:使顶点类型可泛化为字符串、类对象;

  7. 性能优化:使用 std::unordered_map<int, vector<pair<int,int>>> 存稀疏图;

  8. 动态图:支持在线添加/删除顶点。

更多推荐