拓扑排序
- 拓扑排序 模板
- 图的存储结构
- 有权重
- 拓扑排序
- AcWing 848. 有向图的拓扑序列
- 207. 课程表
- 210. 课程表 II
- 蚂蚁三面
- vivo提前批
邻接表存储和遍历的标准模板
拓扑排序 模板
- 拓扑排序
用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++;
}
- 1
- 2
- 3
- 4
- 5
有权重
有权重还需要一个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.
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
拓扑排序
这种方法队列是用数组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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
直接用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
* @date 2021/6/18
*/
public class Tuopu {
static int N = 100010;
// 每个节点的入度
static int[] d = new int[N];
// 邻接表:树的存储由h[],e[],ne[],idx构成
static int[] h = new int[N]; // h[v] 指向节点v形成的链表的头节点
static int[] e = new int[N]; // 第i个点的值(表示下标和值的映射)
static int[] ne = new int[N]; // 表示邻接表链表中 i 节点的下一个节点
static int idx = 1; // 当前遍历到的节点的索引,等边都插入完idx就是节点个数
static int n,cnt = 1; // n个点个数 cnt队列中有多少元素
// 模拟队列
static int[] q = new int[N];
static int hh,tt=-1;
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
// 节点个数n
n = in.nextInt();
// 边的个数m
int m = in.nextInt();
Arrays.fill(h,-1);
for (int i = 0; i < m; i++) {
int a = in.nextInt();
int b = in.nextInt();
// 在节点a的后边插入b
add(a,b);
// 入度+1
d[b]++;
}
// 存在拓扑序列,则输出
if(topsort()){
for (int i = 0; i < n; i++) {
System.out.print(q[i] + " ");
}
}
else{
System.out.println(-1);
}
}
/**
* 在节点a的后面插入b
* @param a
* @param b
*/
private static void add(int a, int b){
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
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;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
207. 课程表
207. 课程表
你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。
在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。
例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。
请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。
示例 1:
输入:numCourses = 2, prerequisites = [[1,0]]
输出:true
解释:总共有 2 门课程。学习课程 1 之前,你需要完成课程 0 。这是可能的。
示例 2:
输入:numCourses = 2, prerequisites = [[1,0],[0,1]]
输出:false
解释:总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0 ;并且学习课程 0 之前,你还应先完成课程 1 。这是不可能的。
提示:
1 <= numCourses <= 105
0 <= <= 5000
prerequisites[i].length == 2
0 <= ai, bi < numCourses
prerequisites[i] 中的所有课程对 互不相同
class Solution {
int N = 100010;
// 邻接表
int[] h = new int[N]; // 邻接表的头节点 h[v] 其中v为节点值
int[] e = new int[N]; // e[i] = v 索引和值的映射
int[] ne = new int[N]; // 下一个节点
int idx = 1;
// 入度
int[] d = new int[N];
// 模拟队列
int[] q = new int[N];
int tt = -1,hh;
public boolean canFinish(int numCourses, int[][] prerequisites) {
// 将所有边都用邻接表表示
Arrays.fill(h,-1);
for(int i=0;i<prerequisites.length;i++){
// 在节点a的后边插入b
add(prerequisites[i][0],prerequisites[i][1]);
// 入度+1
d[prerequisites[i][1]]++;
}
return topSort(numCourses);
}
// 插入节点b
private void add(int a, int b){
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
private boolean topSort(int numCourses){
// 把所有入度为0的入队
for(int i=0;i<numCourses;i++){
if(d[i]==0){
q[++tt] = i;
}
}
// BFS 遍历队列
while(hh<=tt){
// 队首节点出队
int t = q[hh++];
// 找到t对应的邻接表,将其入度都减1
for(int i = h[t];i!=-1;i = ne[i]){
int j = e[i];// i对应的值
if(--d[j] == 0){ // 入度为0 入队
q[++tt] = j;
}
}
}
// 如果所有n个节点都加入过队列,就存在拓扑序
return tt == numCourses -1;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
210. 课程表 II
210. 课程表 II
现在你总共有 n 门课需要选,记为 0 到 n-1。
在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]
给定课程总量以及它们的先决条件,返回你为了学完所有课程所安排的学习顺序。
可能会有多个正确的顺序,你只要返回一种就可以了。如果不可能完成所有课程,返回一个空数组。
示例 1:
输入: 2, [[1,0]]
输出: [0,1]
解释: 总共有 2 门课程。要学习课程 1,你需要先完成课程 0。因此,正确的课程顺序为 [0,1] 。
示例 2:
输入: 4, [[1,0],[2,0],[3,1],[3,2]]
输出: [0,1,2,3] or [0,2,1,3]
解释: 总共有 4 门课程。要学习课程 3,你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。
因此,一个正确的课程顺序是 [0,1,2,3] 。另一个正确的排序是 [0,2,1,3] 。
说明:
输入的先决条件是由边缘列表表示的图形,而不是邻接矩阵。详情请参见图的表示法。
你可以假定输入的先决条件中没有重复的边。
提示:
这个问题相当于查找一个循环是否存在于有向图中。如果存在循环,则不存在拓扑排序,因此不可能选取所有课程进行学习。
通过 DFS 进行拓扑排序 - 一个关于Coursera的精彩视频教程(21分钟),介绍拓扑排序的基本概念。
拓扑排序也可以通过 BFS 完成。
和207一样 只是要把排序输出
class Solution {
int N = 100010;
// 邻接表
int[] h = new int[N]; // 邻接表的头节点 h[v] 其中v为节点值
int[] e = new int[N]; // e[i] = v 索引和值的映射
int[] ne = new int[N]; // 下一个节点
int idx = 1;
// 入度
int[] d = new int[N];
// 模拟队列
int[] q = new int[N];
int tt = -1,hh;
// 存储答案
int[] result;
int index;
public int[] findOrder(int numCourses, int[][] prerequisites) {
result = new int[numCourses];
index = numCourses-1;
// 将所有边都用邻接表表示
Arrays.fill(h,-1);
for(int i=0;i<prerequisites.length;i++){
// 在节点a的后边插入b
add(prerequisites[i][0],prerequisites[i][1]);
// 入度+1
d[prerequisites[i][1]]++;
}
return topSort(numCourses);
}
// 插入节点b
private void add(int a, int b){
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
private int[] topSort(int numCourses){
// 把所有入度为0的入队
for(int i=0;i<numCourses;i++){
if(d[i]==0){
q[++tt] = i;
}
}
// BFS 遍历队列
while(hh<=tt){
// 队首节点出队
int t = q[hh++];
result[index--] = t;
// 找到t对应的邻接表,将其入度都减1
for(int i = h[t];i!=-1;i = ne[i]){
int j = e[i];// i对应的值
if(--d[j] == 0){ // 入度为0 入队
q[++tt] = j;
}
}
}
if (index !=-1) {
return new int[0];
}
return result;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
蚂蚁三面
给n个三元组,例如[‘a’,‘b’,‘N’],[‘c’,‘d’,‘S’],[‘a’,‘d’,‘S’], N和S分别表示前一个节点在后一个节点的北边或南边。 判断给的这个三元组是否成立
vivo提前批
图像从传感器到输出JPEG格式图片经过很多node处理,这些node构成一个图像处理的pipeline,其中的有些节点依赖于其他节点输出。A->B表示B的执行依赖于A。
假设每个node执行时间为A(t),即node A需要执行t秒,没有依赖的node可以并行执行。编写一个方法输入一个有向无环图pipeline,输出执行完需要的最短时间。
【输入】:第一行输入node的执行时间,第二行输入node的依赖关系。
3,1,2,5,3,1
2,3,4;5;5,6;0;0;6;0
【输出】:最短时间。
9