邻接链表图Graph——数据结构系列教程(c++版)

梦想不会自己发光,真正闪耀的是那个为梦狂奔的你。献给知行的孩子们!(Eric.He著)


  图(Graph)是计算机科学中最核心的非线性数据结构之一,用于描述多对多的复杂关系,相比线性表(一对一)、树(一对多),图能更真实地模拟现实世界中的关联场景(如社交网络、交通路网、电路拓扑等)。本教程基于链表实现邻接链表图,从核心原理、代码结构到实战使用,全面讲解邻接链表图的原理与实现,功能包含创建图、添加顶点、添加边、连通测试、最短路径、打印邻接链表、深度优先遍历(DFS)、广度优先遍历(BFS) 等核心功能。

教程目录导航

一、图核心概念

1.1 图的定义

图是由两个有限集合构成的二元组 G = (V, E):

  1. 顶点集(Vertex Set, V):非空有限集合,每个元素称为顶点(Vertex/Node),是图的基本单元(如地图中的城市、社交网络中的用户);
  2. 边集(Edge Set, E):有限集合,每个元素称为边(Edge/Arc),用于连接两个顶点,描述顶点间的关系(如城市间的道路、用户间的好友关系)。

演示动画

1.2 按边的方向分类

  1. 无向图(Undirected Graph):边没有方向,若顶点 v 和 w 间有边,则记为 (v, w),且 (v, w) = (w, v)。
    • 示例:双向好友关系、无向道路(可双向通行);
    • 无向边的度数:一个顶点的度数 = 与该顶点相连的边数(如顶点 0 连接 2、4,则 0 的度数为 2)。
  2. 有向图(Directed Graph/Digraph):边有方向,若从顶点 v 指向 w 的边,记为 <v, w>,且 <v, w> ≠ <w, v>。
    • 示例:单向关注、微信转账(有明确的转出 / 转入方向);
    • 有向边的度数

      入度(In-Degree):指向该顶点的边数(如有向图中顶点 2 指向它的有 0、5,6 则 2 的入度数为 3);

      出度(Out-Degree):从该顶点出发的边数(如有向图中顶点 2 指向 0、5 则 2 的出度数为 2);

      总度数 = 入度 + 出度。

1.3 按边的权重分类

  1. 无权图(Unweighted Graph):边仅表示 “是否相连”,无额外数值属性。
    • 示例:普通社交关系(仅标记是否为好友)。
  2. 带权图(Weighted Graph/Network):边附带一个数值(权重 / 权值,Weight),表示连接的 “代价” 或 “收益”。
    • 示例:交通路网中边的权重为距离 / 时间、电商平台中用户间的互动强度。

1.4 按连通性分类

  1. 连通图(Undirected Connected Graph):无向图中,任意两个顶点间都存在至少一条路径可达。
    • 非连通图:无向图中存在至少两个顶点不可达,每个连通的子图称为连通分量。
  2. 强连通图(Strongly Connected Graph):有向图中,任意两个顶点 v 和 w,既存在 v→w 的路径,也存在 w→v 的路径。
    • 弱连通图:有向图忽略边的方向后是连通图;
    • 强连通分量:有向图中最大的强连通子图。

1.5 其它概念

术语 定义
路径(Path) 从顶点 v 到 w 的顶点序列,序列中相邻顶点间有边相连;
路径长度 无权图中为边的数量,带权图中为边的权重和;
环(Cycle) 起点和终点为同一个顶点的路径(如 A→B→C→A);
简单路径 路径中所有顶点不重复;
完全图 无向完全图中任意两个顶点间都有边(总边数 = n(n-1)/2,n 为顶点数);有向完全图中任意两个顶点间都有双向边(总边数 = n(n-1));
稀疏图 / 稠密图 边数远少于完全图的为稀疏图(如边数 e ≈ n),边数接近完全图的为稠密图(如 e ≈ n²)。

1.6 存储结构

图的存储核心是 “记录顶点信息” 和 “记录顶点间的边关系”,常用方式有邻接矩阵和邻接链表,二者各有优劣。

(1)邻接矩阵(Adjacency Matrix)

邻接矩阵的核心思想是「一维数组 + 二维数组」

顶点数组:用一维数组存储所有顶点的基本信息(如顶点名称vertices);

邻接矩阵:用 n×n 的二维数组 graph 表示 n 个顶点的图

顶点映射:将每个顶点编号为 0~n-1,对应数组的行 / 列索引;

边的表示:

优点 缺点
判断两顶点是否有边:O (1) 空间复杂度:O (n²)(浪费)
计算顶点度数:O (n) 稀疏图中内存利用率极低
实现简单、直观 遍历邻接点:O (n)

(2)邻接链表(Adjacency List)

邻接链表的核心思想是「数组 + 链表」

顶点数组:用一维数组存储所有顶点节点的基本信息(如顶点名称data、第一个邻接边节点地址firstedge);

邻接链表:每个顶点对应一个单向链表,存储该顶点的所有邻接点及边的权重。每一个节点有三个域,即顶点索引adjvex、weight边的权值、next下一个边节点。

优点 缺点
空间复杂度:O (n+e)(高效) 判断两顶点是否有边:O (k)(k 为邻接点数量)
稀疏图中内存利用率极高 实现稍复杂
遍历邻接点:O (k)(k 为邻接点数量) 稠密图中效率不如邻接矩阵

二、代码结构解析

2.1 整体架构

代码分为4个核心部分:

模块 作用 关键变量/结构/函数
全局常量
  • 最大顶点数(可根据需求调整)
  • 表示无边
  • 无边/无穷大标记
  • 路径前驱的无效标记
  • #define MAX_VERTEX 100
  • #define NO_EDGE 0
  • #define INF INT_MAX
  • #define NO_PRE -1
边节点结构体 存储边数据,包含邻接点的顶点索引、边的权值、下一个边节点 EdgeNode
顶点节点结构体 存储顶点数据,包含顶点数据、第一个邻接边节点 VertexNode
邻接链表图结构体 封装所有操作 LinkedGraph

2.2 关键结构体详解

(1)全局常量

            
#define MAX_VERTEX 100  // 最大顶点数(可根据需求调整)
#define NO_EDGE 0       // 表示无边
#define INF INT_MAX     // 无边/无穷大标记
#define NO_PRE -1       // 路径前驱的无效标记
            
        

(2)边节点结构体(EdgeNode)


struct EdgeNode {
    int adjvex;             // 邻接点的顶点索引
    int weight;             // 边的权值
    struct EdgeNode *next; // 指向下一个边节点
};

(3)顶点节点结构体(VertexNode)


struct VertexNode {
    char data;           // 顶点数据(如'A'、'B')
    EdgeNode *firstedge; // 指向第一个邻接边节点
};

(4)邻接链表图结构体(LinkedGraph)

分为私有成员(内部实现)和公有成员(对外接口):


struct LinkedGraph
{
    //------------------------------------------------------------------------------------------------------
    //                                          私有成员
    // 注:私有成员只能在该结构体内部访问调用,外部通过该结构体定义(实体化)的变量(对象)不能访问调用)
    //------------------------------------------------------------------------------------------------------
    private:

        //---------------声明私有成员变量---------------

        //---------------声明私有成员函数---------------
        
        int findMinDist(int dist[], bool visited[], int n);//找到未访问的距离最小的顶点  dist 距离数组  visited 访问标记数组  n 顶点数 距离最小的顶点索引,无则返回-1
        void addEdgeNode(int vIndex, int adjIndex, int weight);// 辅助函数:创建边节点并添加到指定顶点的邻接链表(头插法)
        void printPath(int pre[], int startIndex, int targetIndex);//打印路径(递归回溯前驱数组)  pre 前驱数组  startIndex 起始顶点索引  targetIndex 目标顶点索引
        int dfsCheckConnect(int startIndex, int targetIndex, bool visited[]);//DFS辅助函数:判断从startIndex能否到达targetIndex visited 访问标记数组 能到达返回1,否则返回0
        void dijkstra(int startIndex, int dist[], int pre[]);//Dijkstra算法:计算起始顶点到所有顶点的最短路径 startIndex 起始顶点索引  dist 输出参数:存储起始顶点到各顶点的最短距离  pre 输出参数:存储各顶点的路径前驱索引
        void dfsHelper(int index, bool visited[]);//DFS辅助函数:index 当前顶点索引 visited 访问标记数组

    //------------------------------------------------------------------------------------------------------
    //                                          公共成员
    // 注:公共成员能在该结构体内部访问调用,外部通过该结构体定义(实体化)的变量(对象)也能访问调用)
    //------------------------------------------------------------------------------------------------------
    public:

        /---------------声明公共成员变量---------------

        VertexNode adjList[MAX_VERTEX];// 顶点数组(存储所有顶点)
        int vertexNum;  // 实际顶点数
        int edgeNum;    // 实际边数

        //---------------声明公共成员函数---------------

        // 构造函数:外部通过该结构体定义(实例化)变量(对象)时,自动执行该函数(主要用于初始化成员变量的值)
        LinkedGraph() {
            vertexNum = edgeNum = 0;

            // 初始化所有顶点的邻接表头指针为空
            for (int i = 0; i < MAX_VERTEX; i++) {
                adjList[i].data = '\0';
                adjList[i].firstedge = nullptr;
            }
        }

        int findVertexIndex(char vertex);//查找顶点的索引,未找到返回-1
        int addVertex(char vertex);//添加顶点 vertex 顶点名称(如'A')
        int addEdge(char v1, char v2, int weight);//添加边(无向图)v1 顶点1 v2 顶点2 weight 边的权值(需>0,NO_EDGE=0表示无边)
        int addDirectedEdge(char v1, char v2, int weight);//添加边(有向图)v1 顶点1 v2 顶点2 weight 边的权值(需>0,NO_EDGE=0表示无边)
        void printAdjacency(); // 打印邻接表
        int isConnected(char v1, char v2);//判断两个顶点是否连通 v1 顶点1 v2 顶点2 连通返回1,不连通返回0,顶点不存在返回-1
        int getShortestPath(char start, char target);//获取两顶点的最短路径(封装Dijkstra) start 起始顶点  target 目标顶点  最短路径长度(INF表示无路径,-1表示顶点不存在)
        void dfs(char start);//深度优先遍历(DFS) start 起始顶点
        void bfs(char start);//广度优先遍历(BFS)start 起始顶点
        void freeGraph();// 手动释放图的内存(邻接链表节点)
        // 析构函数:自动销毁邻接边节点,避免内存泄漏
        ~LinkedGraph() {
            freeGraph();
        }
};

三、核心操作实现详解

3.1 添加顶点(addVertex)

添加顶点 vertex 顶点名称(如'A')


int LinkedGraph::addVertex(char vertex)
{
    if (vertexNum >= MAX_VERTEX) {
        printf("顶点数已达上限,无法添加!\n");
        return 0;
    }
    adjList[vertexNum].data = vertex;
    adjList[vertexNum].firstedge = nullptr;
    vertexNum++;
    return 1;
}

3.2 添加边(addEdge)

v1 顶点1 v2 顶点2 weight 边的权值(需>0,NO_EDGE=0表示无边)

(1)无向图(addEdge)


int LinkedGraph::addEdge(char v1, char v2, int weight)
{
    int i = findVertexIndex(v1);
    int j = findVertexIndex(v2);

    if (i == -1 || j == -1 || weight <= 0) {
        return 0;
    }

    // 无向图:双向添加边节点
    addEdgeNode(i, j, weight);
    addEdgeNode(j, i, weight);
    edgeNum++;
    return 1;
}

(2)有向图(addDirectedEdge)


int LinkedGraph::addDirectedEdge(char v1, char v2, int weight)
{
    int i = findVertexIndex(v1);
    int j = findVertexIndex(v2);

    if (i == -1 || j == -1 || weight <= 0) {
        return 0;
    }

    // 有向图:单向添加边节点
    addEdgeNode(i, j, weight);
    edgeNum++;
    return 1;
}
查找顶点的索引(辅助函数)

int LinkedGraph::findVertexIndex(char vertex)
{
    for (int i = 0; i < vertexNum; i++) {
        if (adjList[i].data == vertex) {
            return i;
        }
    }
    printf("顶点%c不存在!\n", vertex);
    return -1;
}
创建边节点(辅助函数)

void LinkedGraph::addEdgeNode(int vIndex, int adjIndex, int weight) {
    EdgeNode *newNode = (EdgeNode *)malloc(sizeof(EdgeNode));
    if (newNode == NULL) {
        printf("内存分配失败!\n");
        exit(1);
    }
    newNode->adjvex = adjIndex;
    newNode->weight = weight;
    newNode->next = adjList[vIndex].firstedge; // 头插法
    adjList[vIndex].firstedge = newNode;
}

3.3 深度优先遍历(DFS)(dfs)


void LinkedGraph::dfs(char start)
{
    int startIndex = findVertexIndex(start);
    if (startIndex == -1) {
        return;
    }

    bool visited[MAX_VERTEX] = {false};
    printf("\n===== 深度优先遍历(起始:%c)=====\n", start);
    dfsHelper(startIndex, visited);
    printf("\n");
}

DFS辅助函数(遍历邻接表)


void LinkedGraph::dfsHelper(int index, bool visited[])
{
    visited[index] = true;
    printf("%c ", adjList[index].data);

    // 遍历当前顶点的所有邻接点(链表)
    EdgeNode *p = adjList[index].firstedge;
    while (p != NULL) {
        int adjIndex = p->adjvex;
        if (!visited[adjIndex]) {
            dfsHelper(adjIndex, visited);
        }
        p = p->next;
    }
}

3.4 广度优先遍历(BFS)(bfs)


void LinkedGraph::bfs(char start)
{
    int startIndex = findVertexIndex(start);
    if (startIndex == -1) {
        return;
    }

    bool visited[MAX_VERTEX] = {false};
    int queue[MAX_VERTEX];
    int front = 0, rear = 0;

    printf("\n===== 广度优先遍历(起始:%c)=====\n", start);

    // 起始顶点入队
    visited[startIndex] = true;
    queue[rear++] = startIndex;

    while (front < rear) {
        int currIndex = queue[front++];
        printf("%c ", adjList[currIndex].data);

        // 遍历当前顶点的所有邻接点(链表)
        EdgeNode *p = adjList[currIndex].firstedge;
        while (p != nullptr) {
            int adjIndex = p->adjvex;
            if (!visited[adjIndex]) {
                visited[adjIndex] = true;
                queue[rear++] = adjIndex;
            }
            p = p->next;
        }
    }
    printf("\n");
}

四、实战使用示例

4.1 使用示例

#include "LinkedRBTree.h"
#include <iostream>
#include <stdio.h>

int main() {
    //  A ── B
    //  │    │
    //  C ── D ── E
    printf("\n===== 图 =====\n");
    printf("  A ── B\n");
    printf("  │    │\n");
    printf("  C ── D ── E\n");

    // 1. 创建图对象
    LinkedGraph graph;

    // 2. 添加顶点(A、B、C、D、E)
    //graph.addVertex('A');
    //graph.addVertex('B');
    //graph.addVertex('C');
    //graph.addVertex('D');
    //graph.addVertex('E');

    // 3. 添加边(有向图)
    graph.addDirectedEdge('A','B',5);
    graph.addDirectedEdge('A','C',3);
    graph.addDirectedEdge('B','D',2);
    graph.addDirectedEdge('C','D',4);
    graph.addDirectedEdge('D','E',1);

    // 3. 打印邻接链表
    graph.printAdjacency();

    // 4. 测试连通性函数
    printf("\n===== 顶点连通性测试 =====\n");
    char v1, v2;

    // 测试1:A和E(连通)
    v1 = 'A'; v2 = 'E';
    int res1 = graph.isConnected(v1, v2);
    if (res1 == 1) {
        printf("顶点%c和%c:连通\n", v1, v2);
    } else if (res1 == 0) {
        printf("顶点%c和%c:不连通\n", v1, v2);
    } else {
        printf("顶点%c或%c不存在\n", v1, v2);
    }

    // 5. 测试最短路径(A -> E)
    graph.getShortestPath('A', 'E'); // A→C→D→E,长度3+4+1=8

    // 6. 深度优先遍历(起始:A)
    graph.dfs( 'A');

    // 7 广度优先遍历(起始:A)
    graph.bfs('A');

    return 0;
}

输出结果


  A ── B
  │    │
  C ── D ── E

  ===== 邻接链表(带权)=====
  A -> (C, 3) (B, 5)
  B -> (D, 2)
  C -> (D, 4)
  D -> (E, 1)
  E ->

  ===== 顶点连通性测试 =====
  顶点A和E:连通

  ===== 最短路径(A -> E) =====
  路径:A -> B -> D -> E
  最短路径长度:3

  ===== 深度优先遍历(起始:A)=====
  A B D C E

  ===== 广度优先遍历(起始:A)=====
  A B C D E

===== 图内存已全部释放 =====

五、完整可运行代码

LinkedGraph.h 头文件


#ifndef LINKEDGRAPH_H_INCLUDED
#define LINKEDGRAPH_H_INCLUDED

#include <limits.h> // 用于INT_MAX
#include <stdbool.h>

//************************************************************************************************************************************************************************
//
//      类名:邻接链表图(LinkedGraph),全称:Adjacency List Graph
//
//      概述:一个可复用的数组邻接链表图,存储结构采用链表实现。
//
//      邻接链表:分为V和E集合,其中,V是顶点,E是边。因此,用一个一维数组存放图中所有顶点数据;用单向链表存储顶点间关系(边或弧)的数据。
//
//      功能:包含创建图、添加顶点、添加边、连通测试、最短路径、打印邻接链表、深度优先遍历(DFS)、广度优先遍历(BFS) 等核心功能。
//
//      说明:
//
//************************************************************************************************************************************************************************


#define MAX_VERTEX 100  // 最大顶点数(可根据需求调整)
#define NO_EDGE 0       // 表示无边
#define INF INT_MAX     // 无边/无穷大标记
#define NO_PRE -1       // 路径前驱的无效标记

//========================================================================================================================================================================
//
//                                                                 边节点结构体(EdgeNode)
//
//========================================================================================================================================================================
struct EdgeNode {
    int adjvex;             // 邻接点的顶点索引
    int weight;             // 边的权值
    struct EdgeNode *next; // 指向下一个边节点
};

//========================================================================================================================================================================
//
//                                                                顶点节点结构体(VertexNode)
//
//========================================================================================================================================================================
struct VertexNode {
    char data;           // 顶点数据(如'A'、'B')
    EdgeNode *firstedge; // 指向第一个邻接边节点
};

//========================================================================================================================================================================
//
//                                                              邻接链表图结构体(LinkedGraph)
//
//========================================================================================================================================================================
struct LinkedGraph
{
    //------------------------------------------------------------------------------------------------------
    //                                          私有成员
    // 注:私有成员只能在该结构体内部访问调用,外部通过该结构体定义(实例化)的变量(对象)不能访问调用)
    //------------------------------------------------------------------------------------------------------
    private:

        //---------------声明私有成员变量---------------

        //---------------声明私有成员函数---------------

        int findMinDist(int dist[], bool visited[], int n);//找到未访问的距离最小的顶点  dist 距离数组  visited 访问标记数组  n 顶点数 距离最小的顶点索引,无则返回-1
        void addEdgeNode(int vIndex, int adjIndex, int weight);// 辅助函数:创建边节点并添加到指定顶点的邻接链表(头插法)
        void printPath(int pre[], int startIndex, int targetIndex);//打印路径(递归回溯前驱数组)  pre 前驱数组  startIndex 起始顶点索引  targetIndex 目标顶点索引
        int dfsCheckConnect(int startIndex, int targetIndex, bool visited[]);//DFS辅助函数:判断从startIndex能否到达targetIndex visited 访问标记数组 能到达返回1,否则返回0
        void dijkstra(int startIndex, int dist[], int pre[]);//Dijkstra算法:计算起始顶点到所有顶点的最短路径 startIndex 起始顶点索引  dist 输出参数:存储起始顶点到各顶点的最短距离  pre 输出参数:存储各顶点的路径前驱索引
        void dfsHelper(int index, bool visited[]);//DFS辅助函数:index 当前顶点索引 visited 访问标记数组

    //------------------------------------------------------------------------------------------------------
    //                                          公共成员
    // 注:公共成员能在该结构体内部访问调用,外部通过该结构体定义(实例化)的变量(对象)也能访问调用)
    //------------------------------------------------------------------------------------------------------
    public:

        //---------------声明私有成员变量---------------

        VertexNode adjList[MAX_VERTEX];// 顶点数组(存储所有顶点)
        int vertexNum;  // 实际顶点数
        int edgeNum;    // 实际边数

        //---------------声明公共成员函数---------------

        // 构造函数:外部通过该结构体定义(实例化)变量(对象)时,自动执行该函数(主要用于初始化成员变量的值)
        LinkedGraph() {
            vertexNum = edgeNum = 0;

            // 初始化所有顶点的邻接表头指针为空
            for (int i = 0; i < MAX_VERTEX; i++) {
                adjList[i].data = '\0';
                adjList[i].firstedge = nullptr;
            }
        }

        int findVertexIndex(char vertex);//查找顶点的索引,未找到返回-1
        int addVertex(char vertex);//添加顶点 vertex 顶点名称(如'A')
        int addEdge(char v1, char v2, int weight);//添加边(无向图)v1 顶点1 v2 顶点2 weight 边的权值(需>0,NO_EDGE=0表示无边)
        int addDirectedEdge(char v1, char v2, int weight);//添加边(有向图)v1 顶点1 v2 顶点2 weight 边的权值(需>0,NO_EDGE=0表示无边)
        void printAdjacency(); // 打印邻接表
        int isConnected(char v1, char v2);//判断两个顶点是否连通 v1 顶点1 v2 顶点2 连通返回1,不连通返回0,顶点不存在返回-1
        int getShortestPath(char start, char target);//获取两顶点的最短路径(封装Dijkstra) start 起始顶点  target 目标顶点  最短路径长度(INF表示无路径,-1表示顶点不存在)
        void dfs(char start);//深度优先遍历(DFS) start 起始顶点
        void bfs(char start);//广度优先遍历(BFS)start 起始顶点
        void freeGraph();// 手动释放图的内存(邻接链表节点)
        // 析构函数:自动销毁邻接边节点,避免内存泄漏
        ~LinkedGraph() {
            freeGraph();
        }
};

#endif // LINKEDGRAPH_H_INCLUDED
                

LinkedGraph.cpp 程序代码


#include  <stdio.h>
#include  <stdlib.h>
#include  <string.h>
#include "LinkedGraph.h"
//---------------实现私有成员函数---------------

// 找到未访问的距离最小的顶点
int LinkedGraph::findMinDist(int dist[], bool visited[], int n) {
    int min = INF;
    int minIndex = -1;

    for (int i = 0; i < n; i++) {
        if (!visited[i] && dist[i] < min) {
            min = dist[i];
            minIndex = i;
        }
    }
    return minIndex;
}

// 辅助函数:创建边节点并添加到指定顶点的邻接链表(头插法)
void LinkedGraph::addEdgeNode(int vIndex, int adjIndex, int weight) {
    EdgeNode *newNode = (EdgeNode *)malloc(sizeof(EdgeNode));
    if (newNode == NULL) {
        printf("内存分配失败!\n");
        exit(1);
    }
    newNode->adjvex = adjIndex;
    newNode->weight = weight;
    newNode->next = adjList[vIndex].firstedge; // 头插法
    adjList[vIndex].firstedge = newNode;
}

// 打印路径(递归回溯前驱数组)
void LinkedGraph::printPath(int pre[], int startIndex, int targetIndex) {
    if (targetIndex == startIndex) {
        printf("%c", adjList[targetIndex].data);
        return;
    }
    if (pre[targetIndex] == NO_PRE) {
        printf("无路径");
        return;
    }
    printPath(pre, startIndex, pre[targetIndex]);
    printf(" -> %c", adjList[targetIndex].data);
}


// DFS辅助函数:判断连通性
int LinkedGraph::dfsCheckConnect(int startIndex, int targetIndex, bool visited[])
{
    if (startIndex == targetIndex) {
        return 1;
    }

    visited[startIndex] = true;

    // 遍历当前顶点的所有邻接点(链表遍历)
    EdgeNode *p = adjList[startIndex].firstedge;
    while (p != NULL) {
        int adjIndex = p->adjvex;
        if (!visited[adjIndex]) {
            if (dfsCheckConnect(adjIndex, targetIndex, visited)) {
                return 1;
            }
        }
        p = p->next;
    }

    return 0;
}

// Dijkstra算法(适配邻接表)
void LinkedGraph::dijkstra(int startIndex, int dist[], int pre[]) {
    int n = vertexNum;
    bool visited[MAX_VERTEX] = {false};

    // 初始化距离和前驱数组
    for (int i = 0; i < n; i++) {
        dist[i] = INF;
        pre[i] = NO_PRE;
    }
    dist[startIndex] = 0; // 起始顶点到自身距离为0
    visited[startIndex] = true;

    // 先初始化起始顶点的直接邻接点距离
    EdgeNode *p = adjList[startIndex].firstedge;
    while (p != NULL) {
        int adjIndex = p->adjvex;
        dist[adjIndex] = p->weight;
        pre[adjIndex] = startIndex;
        p = p->next;
    }

    // 处理剩余n-1个顶点
    for (int i = 1; i < n; i++) {
        int u = findMinDist(dist, visited, n);
        if (u == -1) break;
        visited[u] = true;

        // 松弛操作:遍历u的所有邻接点(链表遍历)
        p = adjList[u].firstedge;
        while (p != NULL) {
            int v = p->adjvex;
            if (!visited[v] && dist[u] != INF) {
                if (dist[u] + p->weight < dist[v]) {
                    dist[v] = dist[u] + p->weight;
                    pre[v] = u;
                }
            }
            p = p->next;
        }
    }
}

// DFS辅助函数(遍历邻接表)
void LinkedGraph::dfsHelper(int index, bool visited[])
{
    visited[index] = true;
    printf("%c ", adjList[index].data);

    // 遍历当前顶点的所有邻接点(链表)
    EdgeNode *p = adjList[index].firstedge;
    while (p != NULL) {
        int adjIndex = p->adjvex;
        if (!visited[adjIndex]) {
            dfsHelper(adjIndex, visited);
        }
        p = p->next;
    }
}

//---------------实现公共成员函数---------------

// 查找顶点的索引,未找到返回-1
int LinkedGraph::findVertexIndex(char vertex)
{
    for (int i = 0; i < vertexNum; i++) {
        if (adjList[i].data == vertex) {
            return i;
        }
    }
    printf("顶点%c不存在!\n", vertex);
    return -1;
}

// 添加顶点
int LinkedGraph::addVertex(char vertex)
{
    if (vertexNum >= MAX_VERTEX) {
        printf("顶点数已达上限,无法添加!\n");
        return 0;
    }
    adjList[vertexNum].data = vertex;
    adjList[vertexNum].firstedge = nullptr;
    vertexNum++;
    return 1;
}

// 添加边(无向图)
int LinkedGraph::addEdge(char v1, char v2, int weight)
{
    int i = findVertexIndex(v1);
    int j = findVertexIndex(v2);

    if (i == -1 || j == -1 || weight <= 0) {
        return 0;
    }

    // 无向图:双向添加边节点
    addEdgeNode(i, j, weight);
    addEdgeNode(j, i, weight);
    edgeNum++;
    return 1;
}

// 添加边(有向图)
int LinkedGraph::addDirectedEdge(char v1, char v2, int weight)
{
    int i = findVertexIndex(v1);
    int j = findVertexIndex(v2);

    if (i == -1 || j == -1 || weight <= 0) {
        return 0;
    }

    // 有向图:单向添加边节点
    addEdgeNode(i, j, weight);
    edgeNum++;
    return 1;
}

// 打印邻接表
void LinkedGraph::printAdjacency()
{
    printf("\n===== 邻接表(带权)=====\n");
    for (int i = 0; i < vertexNum; i++) {
        printf("%c -> ", adjList[i].data);
        EdgeNode *p = adjList[i].firstedge;
        while (p != nullptr) {
            printf("(%c, %d) ", adjList[p->adjvex].data, p->weight);
            p = p->next;
        }
        printf("\n");
    }
}

// 判断两个顶点是否连通
int LinkedGraph::isConnected(char v1, char v2)
{
    int i = findVertexIndex(v1);
    int j = findVertexIndex(v2);

    if (i == -1 || j == -1) {
        return -1;
    }

    if (i == j) {
        return 1;
    }

    bool visited[MAX_VERTEX] = {false};
    return dfsCheckConnect(i, j, visited);
}

// 获取两顶点的最短路径
int LinkedGraph::getShortestPath(char start, char target) {
    int startIndex = findVertexIndex(start);
    int targetIndex = findVertexIndex(target);
    if (startIndex == -1 || targetIndex == -1) {
        return -1;
    }

    if (startIndex == targetIndex) {
        printf("\n===== 最短路径(%c → %c)=====\n", start, target);
        printf("路径:%c\n", start);
        printf("最短路径长度:0\n");
        return 0;
    }

    int dist[MAX_VERTEX];
    int pre[MAX_VERTEX];
    dijkstra(startIndex, dist, pre);

    printf("\n===== 最短路径(%c → %c)=====\n", start, target);
    if (dist[targetIndex] == INF) {
        printf("路径:无\n");
        printf("最短路径长度:∞(无连通路径)\n");
        return INF;
    } else {
        printf("路径:");
        printPath(pre, startIndex, targetIndex);
        printf("\n最短路径长度:%d\n", dist[targetIndex]);
        return dist[targetIndex];
    }
}

// 深度优先遍历
void LinkedGraph::dfs(char start)
{
    int startIndex = findVertexIndex(start);
    if (startIndex == -1) {
        return;
    }

    bool visited[MAX_VERTEX] = {false};
    printf("\n===== 深度优先遍历(起始:%c)=====\n", start);
    dfsHelper(startIndex, visited);
    printf("\n");
}

// 广度优先遍历
void LinkedGraph::bfs(char start)
{
    int startIndex = findVertexIndex(start);
    if (startIndex == -1) {
        return;
    }

    bool visited[MAX_VERTEX] = {false};
    int queue[MAX_VERTEX];
    int front = 0, rear = 0;

    printf("\n===== 广度优先遍历(起始:%c)=====\n", start);

    // 起始顶点入队
    visited[startIndex] = true;
    queue[rear++] = startIndex;

    while (front < rear) {
        int currIndex = queue[front++];
        printf("%c ", adjList[currIndex].data);

        // 遍历当前顶点的所有邻接点(链表)
        EdgeNode *p = adjList[currIndex].firstedge;
        while (p != nullptr) {
            int adjIndex = p->adjvex;
            if (!visited[adjIndex]) {
                visited[adjIndex] = true;
                queue[rear++] = adjIndex;
            }
            p = p->next;
        }
    }
    printf("\n");
}

// 释放图的内存(释放所有邻接链表节点)
void LinkedGraph::freeGraph()
{
    // 遍历每个顶点
    for (int i = 0; i < vertexNum; i++) {
        EdgeNode *p = adjList[i].firstedge;
        // 释放当前顶点的所有邻接边节点
        while (p != nullptr) {
            EdgeNode *temp = p; // 暂存当前节点
            p = p->next;        // 移动到下一个节点
            free(temp);         // 释放当前节点内存
        }
        // 重置头指针
        adjList[i].firstedge = nullptr;
    }

    // 重置顶点数和边数
    vertexNum = 0;
    edgeNum = 0;
    printf("\n===== 图内存已全部释放 =====\n");
}

main.cpp 测试代码

#include "LinkedGraph.h"
#include <iostream>
#include <string>

int main() {
    //  A ── B
    //  │    │
    //  C ── D ── E
    printf("\n===== 图 =====\n");
    printf("  A ── B\n");
    printf("  │    │\n");
    printf("  C ── D ── E\n");

    //使用MyLibrary函数库中的ArrayGraph(邻接矩阵图结构体)定义graph变量
    //ArrayGraph graph;

    //使用MyLibrary函数库中的LinkedGraph(邻接链表图结构体)定义graph变量
    LinkedGraph graph;

    // 1. 添加顶点(A、B、C、D、E)
    graph.addVertex('A');
    graph.addVertex('B');
    graph.addVertex('C');
    graph.addVertex('D');
    graph.addVertex('E');

    // 2. 添加边(无向图)
    //graph.addEdge('A','B',1);
    //graph.addEdge('A','C',1);
    //graph.addEdge('B','D',1);
    //graph.addEdge('C','D',1);
    //graph.addEdge('D','E',1);

    // 2. 添加边(有向图)
    graph.addDirectedEdge('A','B',5);
    graph.addDirectedEdge('A','C',3);
    graph.addDirectedEdge('B','D',2);
    graph.addDirectedEdge('C','D',4);
    graph.addDirectedEdge('D','E',1);

    // 3. 打印邻接链表
    graph.printAdjacency();

    // 4. 测试连通性函数
    printf("\n===== 顶点连通性测试 =====\n");
    char v1, v2;

    // 测试1:A和B(连通)
    v1 = 'A'; v2 = 'E';
    int res1 = graph.isConnected(v1, v2);
    if (res1 == 1) {
        printf("顶点%c和%c:连通\n", v1, v2);
    } else if (res1 == 0) {
        printf("顶点%c和%c:不连通\n", v1, v2);
    } else {
        printf("顶点%c或%c不存在\n", v1, v2);
    }

    // 5. 测试最短路径(A -> E)
    graph.getShortestPath('A', 'E'); // A→C→D→E,长度3+4+1=8

    // 6. 深度优先遍历(起始:A)
    graph.dfs( 'A');

    // 7 广度优先遍历(起始:A)
    graph.bfs('A');

return 0;
}

输出结果


  A ── B
  │    │
  C ── D ── E

  ===== 邻接链表(带权)=====
  A -> (C, 3) (B, 5)
  B -> (D, 2)
  C -> (D, 4)
  D -> (E, 1)
  E ->

  ===== 顶点连通性测试 =====
  顶点A和E:连通

  ===== 最短路径(A -> E) =====
  路径:A -> B -> D -> E
  最短路径长度:3

  ===== 深度优先遍历(起始:A)=====
  A B D C E

  ===== 广度优先遍历(起始:A)=====
  A B C D E

===== 图内存已全部释放 =====

六、总结

本文详细解析了邻接链表图(LinkedGraph)的原理与代码实现,核心要点:

该实现可直接复用,也可根据需求扩展(如支持负权边的Bellman-Ford算法、拓扑排序等),是学习图结构和算法的优质实践案例。


返回顶部