拓扑排序 模板
- 拓扑排序
用bfs来访问所有的节点,把入度为0的节点加入到队列中;每次从队列中取出一个节点,相当于从图删除这个节点,这样此节点的后继节点的入度都减1,再把入度为0的节点加到队列中。 - 时间复杂度
时间复杂度 O(n+m), n 表示点数,m 表示边数 - java 模板
用邻接表存储
BFS遍历队列
图的存储结构
由于树是特殊的图,所以我们存储树也是用的邻接表或邻接图。
邻接表是指对于图上的每个结点来说,用一个单链表存储它的临接点。
我们需要h[],e[],ne[],idx来表示这个邻接点:
- h[v] : 指向节点v形成的链表的头节点
- e[i] : 第i个点的值v(表示下标和值的映射)
- ne[i] : 表示邻接表链表中 i 节点的下一个节点
- idx : 当前遍历到的节点的索引,等边都插入完idx就是节点个数
在节点a的后面插入b:
idx为当前遍历到的节点即b,因此e[idx] = b
我们用头插法插入到a的那条邻接表,h[a]为a的邻接表的头节点,所以ne[idx] = h[a]
头节点变成我们新插入的节点b
private static void add(int a, int b){
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
有权重
有权重还需要一个w[i]来存储当前边的权重
- w[i]: 指i的权重
构建邻接表:
其中a->b, c为a到b的权重
void add(int a, int b, int c) {
//构建邻接表
e[idx] = b;
w[idx] = c;
ne[idx] = h[a];
h[a] = idx++; //h[a] 一直指向a邻接表头插法起点,其实是最后一个,指针保留的方式也是向前
}
1. idx一直向前,如果a是第一次出现,则h[a]的值对应ne中位置即是起点。
2. 插入的方式是类似头插法,每次邻接表中的新元素出现,则插入邻接链表的第一个。也可以这样理解,是每次插到最后,让h[a]指向最后一个元素,遍历的时候倒着向前遍历。
3. 如果指向下一个为空时,指针值为-1.
拓扑排序
这种方法队列是用数组q表示的,hh指向队首节点 初始化为0,tt指向最后一个节点初始化为-1;
当hh<=tt 说明队列中有元素,就进行遍历。
最后如果所有元素都入过队,即tt==n-1 就说明有拓扑序列
private static boolean topsort(){
// 把入度为0的节点入队
for (int i = 1; i <= n; i++) {
// i是点的编号 看题目的编号是如何给的
if(d[i]==0){
q[++tt] = i;//入队
}
}
// BFS 访问
while(hh <= tt){
// 取队头节点t
int t = q[hh++];
// 遍历t的单链表,删除节点t,所以将节点t的所有后继j的入度-1
for (int i = h[t]; i != -1; i = ne[i]) {
int j = e[i]; // 索引对应的值 因为入度用值作为下标
if(--d[j] == 0){
q[++tt] = j; // 入度为0加入队列
}
}
}
// 如果所有n个节点都加入过队列,就存在拓扑序
return tt == n - 1;
}
直接用Queue表示队列
AcWing 848. 有向图的拓扑序列
【题目描述】
给定一个n个点m条边的有向图,点的编号是1到n,图中可能存在重边和自环。
请输出任意一个该有向图的拓扑序列,如果拓扑序列不存在,则输出-1。
若一个由图中所有点构成的序列A满足:对于图中的每条边(x, y),x在A中都出现在y之前,则称A是该图的一个拓扑序列。
【输入格式】
第一行包含两个整数n和m
接下来m行,每行包含两个整数x和y,表示存在一条从点x到点y的有向边(x, y)。
【输出格式】
共一行,如果存在拓扑序列,则输出拓扑序列。
否则输出-1。
【数据范围】
1≤n,m≤105
【输入样例】
3 3
1 2
2 3
1 3
【输出样例】
1 2 3
import java.util.Arrays;
import java.util.Scanner;
/**
* @author flora.zxf
* @date 2021/6/18
*/
public class Tuopu {
static int N = 100010