链式前向星
一,前言
说实话,看到时候没有看懂,自己模拟了一下边是怎么加上去的,才搞明白。想明白之后,才知道被这些变量名给忽悠了,我不知道取名的人是怎么想的,但我觉的有些变量名并不恰当,直接把我带到沟里去了,所以这里我写的变量名是根据自己的理解命名。๑乛◡乛๑ (●´∀`●)
二,引子
1,
想要通过一组有序的点来记录一棵树的话,你只需要两个变量,一个记录子节点的编号,一个记录父节点的编号。但如果存的是一张图,又有什么不同呢?
在一棵树中,子节点只有一个父节点,而如果是图的话,子节点就可能有多个父节点,出现 一点映射多点的情况,这样你就不知道要设几个变量来存前一个点的编号了。
2,
为了解决这个问题,有人想出了用足够多的空间存父节点的编号——邻接矩阵。
但邻接矩阵的时间和空间复杂度都不够理想,于是又有人想出邻接矩阵的优化——邻接表。
但邻接表写起来又太麻烦了,于是又有人想出另外一种办法存图——链式前向星。
3,
邻接矩阵和邻接表都是根据存点的信息再推出边的信息,进而存图,而链式前向星是直接存边的信息(边的信息自然包括顶点的信息),然后直接由边的信息推出一张图。
三,链式前向星
1,那么一条边需要存什么信息呢?
很容易想到的是:起点,终点,权值。但是有个问题:如何通过这条边得到它的所有相邻边的信息?
如果是通过这条边的终点去匹配其他所有边的起点的,其本质也就跟邻接矩阵差不多。因此,链式前向星并不是简单的存上述三种变量,那么,它需要存的是什么信息呢?
2,邻接边的链接 o_o ....
链式前向星首先对所有的边进行编号;
然后在每条边中,链式前向星放弃了起点的保存,并在每一条边存的信息加上一个值 pre —— 当前边的起点的所有邻接边中,位于当前边的前一条邻接边的编号;
于是,为了找到某个点的所有邻接边中最后一条边的编号,我们需要另外设一个数组 last[]。last[i] —— 点i 的所有邻接边中最后一条边的编号;
最后,以BFS为例:
先用 last[i] 得到根节点的所有邻接边中最后一条边的编号,
再利用 pre 往前遍历所有的邻接边,与此同时,将这些邻接边的终点放入队列中,作为下次搜索的起点。
最后,不断取出队列中的点,重复上述操作。
3,总结
① 我们需要 一个结构体数组 e 记录边的信息,结构体里面的变量有:pre,to,w。
对于第 i 条边 e[i] 里的变量来说,有:
to: 编号为 i 的边是以某个点为起点的第 x 条边,其对应的终点为—— to
w: 编号为 i 的边是以某个点为起点的第 x 条边,其对应的权值为—— w
pre:编号为 i 的边是以某个点为起点的第 x 条边,其对应的同起点的第 x-1 条边编号为—— pre
其中:我们根本不 care x 的值是多少,只需知道它能通过 pre 往前递推就是了。
② last[]:last[i] —— 点i 的所有邻接边中最后一条边的编号。
四,利用链式前向星遍历图
利用 pre 进行横向搜索:
以某个点为起点的所有边的遍历。
利用 last[] 和to 进行向下搜索:
通过某条边的终点,找到以该边终点为起点的所有邻接边中最后一条边的编号。
五,完整代码:
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #include<string.h> struct Chain_forward_star { #define N (int)2e6+5 int last[N], tot; struct Node { int pre, to, w; }e[N]; void init() // 初始化 { tot = 0; // 链式前向星中边的数量,初始为 0 memset(last, -1, sizeof(last)); } void add(int from, int to, int w) // 形参按顺序为: 起点,终点,权值 { tot++; e[tot].to = to; e[tot].w = w; e[tot].pre = last[from]; // 记录与新加的边同一起点的所有边中,前一条边的编号 last[from] = tot; // 更新 last 数组,新加入的边成为当前边的起点的所有邻接边中最后一条边的编号 } }star; int vis[N]; void dfs(int s) // 遍历图 { vis[s] = 1; for (int i = star.last[s]; ~i; i = star.e[i].pre) // 遍历以 s 为起点的所有边 { printf("编号为 %d 的边: 起点:%d 终点:%d 权值:%d\n", i, s, star.e[i].to, star.e[i].w); if (!vis[star.e[i].to]) // 以第 i 条边的终点为起点,向下搜索 dfs(star.e[i].to); } } int main(void) { int m; // 边数 while (scanf("%d", &m) != EOF) { star.init(); memset(vis, 0, sizeof(vis)); for (int i = 1; i <= m; i++) { int x, y, z; scanf("%d%d%d", &x, &y, &z); star.add(x, y, z); } dfs(1); // 从 点1 开始遍历 } return 0; } /* 测试数据:有向图 3 1 2 1 2 3 2 2 4 3 */
========== ========= ========= ======= ===== ===== === == =
渡荆门送别 唐 李白
渡远荆门外,来从楚国游。
山随平野尽,江入大荒流。
月下飞天镜,云生结海楼。
仍怜故乡水,万里送行舟。