Bootstrap

链式前向星:邻接表的优化实现与代码详解

引言:为什么需要链式前向星?

图的存储是图论算法的基础,常见的存储方式包括邻接矩阵邻接表。邻接矩阵简单直观,但空间复杂度为 O(n2),在稀疏图中效率低下;邻接表虽节省空间,但依赖指针操作,实现复杂且容易出错。
链式前向星应运而生——它通过数组模拟指针,既保留了邻接表的空间效率O(∣V∣+∣E∣),又避免了指针的复杂性。本文将结合代码实现与案例,深入解析链式前向星的核心逻辑。

核心概念与代码结构

数据结构设计

链式前向星的核心是边数组头指针数组

struct Edge {
    int to;   // 边的终点
    int w;    // 边的权值
    int net;  // 同一顶点的下一条边的编号
} e[10005];   // 边数组

int head; // head[i]表示顶点i的第一条边的编号
int cut = 0;   // 当前边的编号(从0开始)

初始化

初始化时,所有顶点的头指针置为 -1,表示无连接边:

memset(head, -1, sizeof(head));
cut = 0;

关键操作解析

添加边:头插法

添加边 x→y(权值 w)时,采用头插法更新链表:

void add(int x, int y, int w) {
    e[cut].to = y;
    e[cut].w = w;
    e[cut].net = head[x]; // 新边的下一条边是当前头指针指向的边
    head[x] = cut;        // 更新头指针为当前边
    cut++;
}
  • 头插法逻辑:新边插入链表头部,时间复杂度为 O(1)O(1)。

无向图处理

add(x, y, w); 
add(y, x, w); // 反向边

遍历与输出

遍历逻辑

遍历所有顶点,通过头指针依次访问每条边:

void print() {
    cout << "-链式前向星如下:------------------------------------\n";
    for (int i = 1; i <= n; i++) {
        for (int j = head[i]; j != -1; j = e[j].net) {
            int end = e[j].to, w = e[j].w;
            cout << i << "->" << end << " " << w << "\t";
        }
        cout << endl;
    }
}
  • 嵌套循环:外层遍历顶点,内层遍历顶点的所有邻接边。

案例验证

案例1:无向图

输入

4 5
1 2 5
1 4 3
2 3 8
2 4 12
3 4 9

输出

1->4 3      1->2 5
2->4 12     2->3 8      2->1 5
3->4 9      3->2 8
4->3 9      4->2 12     4->1 3

说明:每个顶点的邻接边以头插法倒序存储。

案例2:有向图

输入

5 6
1 2 1
1 3 2
2 3 3
2 4 4
3 4 5
4 5 6

输出

顶点1的邻接边:1->3(权2)	1->2(权1)	
顶点2的邻接边:2->4(权4)	2->3(权3)	
顶点3的邻接边:3->4(权5)	
顶点4的邻接边:4->5(权6)	
顶点5的邻接边:

完整代码奉上

#include<bits/stdc++.h>
using namespace std;
struct Edge {
    int to;//某条边的终点
    int w;//某条边的边权
    int net;//具有相同起点的下一条边
} e[10005]; //下标表示边的编号 从0开始
int head[105];
int n, m; //n个点 m条边
int cut;//实际边的数目,边的编号
void add(int x, int y, int w) {
    //编号为cut的边:x->y(边权为w)
    e[cut].to = y;
    e[cut].w = w;
    //类似头插法,将原来已经存好的边接到e[cut]后面
    //然后将该边更新到x边的后面
    e[cut].net = head[x];
    head[x] = cut;
    cut++;//接收下一条边
}
//链式前向星的遍历
void print() {
    cout << "----------链式前向星如下:----------" << endl;
    for (int i = 1; i <= n; i++) {
        for (int j = head[i]; j != -1; j = e[j].net)
            //找i的边 先令j=head[i](与i有关的边的编号)   如果 j!=-1 (说明存在与i点相连的边) 那么继续寻找下一条边(与i有关)
        {
            int end = e[j].to, w = e[j].w;
            //i->end  w(边权)
            cout << i << " " << "-> " << end << "  " << w << "\t";
        }
        cout << endl;
    }
}
int main() {
    int x, y, w;
    cin >> n >> m;
    //如果一个点无边相连,令其head数组的值为-1
    memset(head, -1, sizeof(head));
    cut = 0;
    for (int i = 0; i < m; i++) {
        cin >> x >> y >> w;
        //无向图
        add(x, y, w);//添加边
        add(y, x, w);//添加反向边
    }
    //点的编号从1开始
    print();
    return 0;
}

链式前向星的优缺点

优点

  1. 空间高效:仅存储有效边,空间复杂度 O(∣V∣+∣E∣)点数加边数  。
  2. 无指针操作:通过数组索引模拟链表,避免内存泄漏风险。
  3. 快速插入:头插法保证每次添加边的时间为 O(1)。

缺点

  1. 遍历顺序逆序:头插法导致邻接边的输出顺序与输入顺序相反。
  2. 静态数组限制:需预先分配足够大的边数组,可能浪费空间。

结语:何时选择链式前向星?

链式前向星适合处理稀疏图,尤其是需要频繁遍历邻接边的场景(如DFS/BFS、最短路径算法)。其简洁的实现和高效的空间利用率,使其成为竞赛和工程中的常用选择。
希望本文能帮助你理解链式前向星的实现细节。

完整代码与测试案例已附上,欢迎在评论区讨论优化与改进方案!

;