引言:为什么需要链式前向星?
图的存储是图论算法的基础,常见的存储方式包括邻接矩阵和邻接表。邻接矩阵简单直观,但空间复杂度为 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;
}
链式前向星的优缺点
优点
- 空间高效:仅存储有效边,空间复杂度 O(∣V∣+∣E∣)点数加边数 。
- 无指针操作:通过数组索引模拟链表,避免内存泄漏风险。
- 快速插入:头插法保证每次添加边的时间为 O(1)。
缺点
- 遍历顺序逆序:头插法导致邻接边的输出顺序与输入顺序相反。
- 静态数组限制:需预先分配足够大的边数组,可能浪费空间。
结语:何时选择链式前向星?
链式前向星适合处理稀疏图,尤其是需要频繁遍历邻接边的场景(如DFS/BFS、最短路径算法)。其简洁的实现和高效的空间利用率,使其成为竞赛和工程中的常用选择。
希望本文能帮助你理解链式前向星的实现细节。
完整代码与测试案例已附上,欢迎在评论区讨论优化与改进方案!