图论——链式前向星

2022年 10月 11日 99点热度 0人点赞

整理自: https://blog.csdn.net/qq_35501306/article/details/106276964

链式前向星概括

以同起点为一条链, 数组 $head[a]$ 存储起点 $a$ 的最新录入的一条边的索引, 每条边以结构体的形式存储该边信息 (该边终点, 权值, 同起点的边中的上一条边即上一次录入的边的位置), 所有边构成一个结构体数组.

很多帖子说到的是存储的是下一条边, 这种理解很容易给人误导, 应该是每一条边都能通过自身结构体存储的信息回溯到上一条边, 所以叫前向星.

假如我们要找出一条 $a \rightarrow b$ 的边, 先根据 $head[a]$ 找出从起点 $a$ 出发的所有边里最新录入的那条边, 然后根据该边找出它的上一条边, 再根据上一条边找出它的上上一条边, 直到找到一条终点为 $b$ 的边为止. 整个过程是链式回溯的, 所以是链式前向星.

链式前向星范例

file

边的录入顺序 (起点 $a \rightarrow 终点 b = 权值 w$):

  • 第 $0$ 条边: $5 \rightarrow 3 = 1$, 此时 $head[5]=0$ 以 $5$ 为起点没有上一条边
  • 第 $1$ 条边: $1 \rightarrow 2 = 9$, 此时 $head[1]=1$ 以 $1$ 为起点没有上一条边
  • 第 $2$ 条边: $3 \rightarrow 5 = 11$, 此时 $head[3]=2$ 以 $3$ 为起点没有上一条边
  • 第 $3$ 条边: $2 \rightarrow 1 = 5$, 此时 $head[2]=3$ 以 $2$ 为起点没有上一条边
  • 第 $4$ 条边: $4 \rightarrow 5 = 6$, 此时 $head[4]=4$ 以 $4$ 为起点没有上一条边
  • 第 $5$ 条边: $3 \rightarrow 1 = 7$, 此时 $head[3]=5$ 以 $3$ 为起点的上一条边的是第 $2$ 条边
  • 第 $6$ 条边: $3 \rightarrow 4 = 4$, 此时 $head[3]=6$ 以 $3$ 为起点的上一条边的是第 $5$ 条边
  • 第 $7$ 条边: $4 \rightarrow 1 = 2$, 此时 $head[4]=7$ 以 $4$ 为起点的上一条边的是第 $4$ 条边

假如我们要找 $3 \rightarrow 5$ 这条边:

  • 根据 $head[3]=6$, 得到起点 $3$ 的最新一条录入的边是第 $6$ 条边
  • 根据 $edge[6].to=4$, 这条是 $3 \rightarrow 4$ 的边, 不符合, 我们继续找
  • 根据 $edge[6].pre=5$, 得到第 $6$ 条边的上一条同起点边是第 $5$ 条边
  • 根据 $edge[5].to=1$, 这条是 $3 \rightarrow 1$ 的边, 不符合, 我们继续找
  • 根据 $edge[5].pre=2$, 得到第 $5$ 条边的上一条同起点边是第 $2$ 条边
  • 根据 $edge[2].to=5$, 这条是 $3 \rightarrow 5$ 的边, 找到了

链式前向星实现

具体实现

边的结构体定义, 存储该边信息

struct Edge{
    int to;  // 表示该边的终点
    int pre; // 同起点的上一条边的位置, 值为-1 表示没有上一条边
    int w;   // 该边权值
};

定义链头 $head$, 存储每个起点的最新一条边的索引, $-1$ 表示该点无边

int head[n+1];
memset(head,-1, sizeof(head));

定义结构体数组 $edge$, 存储每一次录入的边

Edge edge[2*m+2];

定义一个边的计数器 $ecnt$, 表示当前已录入边的数量

int ecnt=0;

增边函数, 增加一条 $a \rightarrow b$ 的权值为 $w$ 的边

inline void addEdge(int a, int b, int w, int head[], Edge edge[], int &ecnt){
    edge[ecnt].w=w;
    edge[ecnt].to=b;
    edge[ecnt].pre=head[a];   //该边上浮为起点 a 的所有边里最新的边
    head[a]=ecnt++;
}

无向图的存储

addEdge(a, b, w, head, edge, ecnt);
addEdge(b, a, w, head, edge, ecnt);

每次加边的时候都需要反向加边.

完整代码

//
// Created by duhbb on 2022/10/12.
//
#include<iostream>
#include<fstream>
#include<cstring>

using namespace std;

// 边的结构体定义
struct Edge {
    int to;  // 表示该边的终点
    int pre; // 同起点的上一条边的位置, 值为 -1 表示没有上一条边
    int w;   // 该边权值
};

//增加一条 a -> b 的权值为 w 的边
inline void addEdge(int a, int b, int w, int head[], Edge edge[], int &ecnt) {
    edge[ecnt].w = w;
    edge[ecnt].to = b;
    edge[ecnt].pre = head[a];//该边上浮为起点 a 的所有边里最新的边
    head[a] = ecnt++;
}

int main() {
    ifstream ifs("./链式前向星-input.txt");
    int n, m;//n 表示顶点数, m 表示边数
    ifs >> n >> m;

    //初始化
    //head[a]表示顶点 a 的最新一条边的数组下标,-1 表示该点无边
    int head[n + 1];
    memset(head, -1, sizeof(head));
    Edge edge[2 * m + 2];
    //当前已录入边的数量
    int ecnt = 0;

    for (int i = 0; i < m; i++) {
        int a, b, w;//边 a 到边 b 的权值 w
        ifs >> a >> b >> w;
        addEdge(a, b, w, head, edge, ecnt);
        //无向图, 每次都需要反向加边
//      addEdge(b, a, w, head, edge, ecnt);

//      //调试用, 输出有向图每条边的情况
//      cout<<" 第"<<i<<" 条边:"
//      <<a<<" -> "<<edge[i].to<<" = "<<edge[i].w
//      <<"\t 此时 head["<<a<<"]="<<head[a]
//      <<"\t 以"<<a<<" 为起点";
//      if(edge[i].pre==-1)
//          cout<<" 没有上一条边";
//          else
//              cout<<" 的上一条边是第"<<edge[i].pre<<" 条边";
//      cout<<endl;

//      //调试用, 输出无向图每条边的情况
//      cout<<" 第"<<2*i<<" 条边:"
//      <<a<<" -> "<<edge[2*i].to<<" = "<<edge[2*i].w
//      <<"\t 此时 head["<<a<<"]="<<head[a]
//      <<"\t 以"<<a<<" 为起点";
//      if(edge[2*i].pre==-1)
//          cout<<" 没有上一条边";
//          else
//              cout<<" 的上一条边是第"<<edge[2*i].pre<<" 条边";
//      cout<<endl;
//
//      cout<<" 第"<<2*i+1<<" 条边:"
//      <<b<<" -> "<<edge[2*i+1].to<<" = "<<edge[2*i+1].w
//      <<"\t 此时 head["<<b<<"]="<<head[b]
//      <<"\t 以"<<b<<" 为起点";
//      if(edge[2*i+1].pre==-1)
//          cout<<" 没有上一条边";
//          else
//              cout<<" 的上一条边的是第"<<edge[2*i+1].pre<<" 条边";
//      cout<<endl;
    }
    ifs.close();
    //遍历输出, 有向图和无向图均适用
    for (int a = 1; a <= n; a++) {//起点 a
        cout << a;
        for (int b = head[a]; b != -1; b = edge[b].pre) {//终点 b

            cout << " -> " << edge[b].to;
        }
        cout << endl;
    }
    return 0;
}

样例输入

有向图

5 8
5 3 1
1 2 9
3 5 11
2 1 5
4 5 6
3 1 7
3 4 4
4 1 2

无向图

9 14
1 2 4
2 3 8
3 4 7
4 5 9
5 6 10
6 7 2
7 8 1
8 9 7
2 8 11
3 9 2
7 9 6
3 6 4
4 6 14
1 8 8

样例输出

有向图

无向图

重边的处理 (待补充)

链式前向星+Prim 算法 (待补充)

链式前向星优缺点

优点

  1. 内存利用率高: 可以准确开辟最多边数的内存, 不像邻接表有爆内存的风险.
  2. 对不确定边的操作方便效率也不错, 这点和邻接表一样, 不会遍历到不存在的边.
  3. 几乎可以用于全部的图论题目

缺点

  1. 难于理解, 代码较复杂
  2. 重边不好处理, 这点与邻接表一样, 只有通过遍历判重.
  3. 对确定边的操作效率不高, 也与邻接表一样, 不能通过两点马上确定边, 只能遍历查找.

rainbow

这个人很懒,什么都没留下

文章评论