开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第34天,点击查看活动详情
✨欢迎关注🖱点赞🎀收藏⭐留言✒
🔮本文由京与旧铺原创,csdn首发!
😘系列专栏:算法学习
💻首发时间:🎞2022年12月29日🎠
🀄如果觉得博主的文章还不错的话,请三连支持一下博主哦
🎧作者是一个新人,在很多方面还做的不好,欢迎大佬指正,一起学习哦,冲冲冲
1.拓扑排序
拓扑排序(Topological Sorting),顾名思义,它是一种排序的算法,更准确的说,它是一种给图排序的算法,但是这张图呢,不是随便一张图都有拓扑排序,只有 有向无环图(DAG, Directed Acyclic Graph) 才存在拓扑排序序列,实质上它是对有向无环图的顶点排成一个线性序列,图中任意一对顶点u和v,若边<u,v>∈E(G),则u在线性序列中出现在v之前。
拓扑排序得到的序列满足以下特点:
- 每个顶点出现且只出现一次。
- 若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面。
例如:
这张图的拓扑排序后的序列为:1 2 3,根据图中的指向,结点1无论如何都会指向2或3,所以结点1会排在结点2和3的前面,结点2无论如何都会指向3,所以结点2排在结点3前,综合拓扑序列为1 2 3,当然,拓扑排序并不是唯一的。
如下面这一张图的拓扑排序序列为:1 5 6 2 4 8 3 7
当然不难发现,拓扑序列并不是唯一的,上图中相同颜色的节点调换位置,也还是一个拓扑排序,如5 1 6 2 4 8 3 7,1 5 2 6 4 8 3 7等。
那拓扑排序有什么作用,拓扑排序有什么意义呢?
举一个栗子,拓扑排序就相当于一个学习路线,比如学java路线,依次要学c,java,数据结构,数据库,servlet,spring,spring boot,mybatis等,但是这不是唯一的学习路线,因为学习顺序不是死的,你只要满足学习一门学科时,有必须的基础就可以了,就好比学数据结构前,你需要先学习一门编程语言,至于你先学c还是先学java是无所谓的,但是编程语言的学习一定在学数据结构之前。
2.拓扑排序实现
2.1实现思路
拓扑排序实现的思路就是BFS,即广度优先搜索,在谈论实现之前,我们先来了解一个概念,就是图的度。
对于图的度,分为出度和入度,出度就是结点指出边的条数(该结点指向其他结点的个数),入度就相反,就是指入边的条数(其他结点指向该结点的个数)。
我们知道图的实现可以使用邻接矩阵和邻接表实现,本文统一以邻接表的形式实现。
实现的思路与BFS类似:
- 首先我们将所有入度为
0的结点放入队列当中。 - 第二步,取出队列中队头结点,遍历该结点所有指出的结点,并将指向遍历结点的边删除,即就是将入度减
1。 - 第三步,删除边后,遍历结点的入度为
0则将该结点入队。 - 第四步,结点遍历完后,将队头结点存入顺序表或数组中(直接使用数组实现的队列,可不实现这一步,因为实现队列的数组就是一个记录顺序的表)。
- 第五步,判断顺序表中元素个数是否与图中结点个数相同,相同表示存在拓扑排序,否则不存在。
- 第六步,输出拓扑序列。
2.2实现代码
题目链接
给定一个 n 个点 m 条边的有向图,点的编号是 1 到 n,图中可能存在重边和自环。
请输出任意一个该有向图的拓扑序列,如果拓扑序列不存在,则输出 −1。
若一个由图中所有点构成的序列 A 满足:对于图中的每条边 (x,y),x 在 A 中都出现在 y 之前,则称 A 是该图的一个拓扑序列。
输入格式
第一行包含两个整数 n 和 m。
接下来 m 行,每行包含两个整数 x 和 y,表示存在一条从点 x 到点 y 的有向边 (x,y)。
输出格式
共一行,如果存在拓扑序列,则输出任意一个合法的拓扑序列即可。
否则输出 −1。
数据范围
输入样例:
3 3
1 2
2 3
1 3
输出样例:
1 2 3
解题思路: 本题就是实现拓扑排序,按照上述分析思路实现即可。
源代码: Java代码:
import java.util.*;
class Main {
private static final int N = (int) 1e5 + 2333;
//邻接表
private static int[] h = new int[N], e = new int[N], en = new int[N];
private static int[] d = new int[N];
private static int idx = 0;
//队列
private static int[] q = new int[N];
private static int n, m;
private static void add(int a, int b) {
e[idx] = b;
en[idx] = h[a];
h[a] = idx++;
}
private static boolean topsort() {
//将入度为0的结点加入对列
int head = 0;
int tail = -1;
for (int i = 1; i <= n; i++) {
if (d[i] == 0) q[++tail] = i;
}
//bfs
while (head <= tail) {
int top = q[head++];
for (int i = h[top]; i != -1; i = en[i]) {
int j = e[i];
d[j]--;
if (d[j] == 0) q[++tail] = j;
}
}
//System.out.println(tail + " " + n);
return tail == n - 1;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
m = sc.nextInt();
Arrays.fill(h, -1);
while (m-- > 0) {
int a = sc.nextInt();
int b = sc.nextInt();
add(a, b);
d[b]++;
}
//如果存在拓扑序列就输出
if (topsort()) {
for (int i = 0; i < n; i++) {
System.out.print(q[i] + " ");
}
System.out.println();
} else {
System.out.println(-1);
}
}
}
C++代码:
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = (int) 1e5 + 2333;
int n, m;
//邻接矩阵
int h[N], e[N], en[N];
//队列,入度
int q[N], d[N];
int idx = 0;
//添加边
void add(int a, int b)
{
e[idx] = b;
en[idx] = h[a];
h[a] = idx++;
}
//判断是否有拓扑序列
bool topsort()
{
int head = 0, tail = -1;
//将度为0的点加入队列
for (int i = 1; i <= n; i++)
{
if (!d[i]) q[++tail] = i;
}
while (head <= tail)
{
int top = q[head++];
for (int i = h[top]; i != -1; i = en[i])
{
int j = e[i];
d[j]--;
if (d[j] == 0) q[++tail] = j;
}
}
return tail == n - 1;
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof(h));
//添加所有的边
while (m-- > 0)
{
int a, b;
cin >> a >> b;
add(a, b);
//b入度加1
d[b]++;
}
if (topsort())
{
//最终队列中元素的顺序就是一个拓扑序列(拓扑序列不唯一)
for (int i = 0; i < n; i++)
{
cout << q[i] << " ";
}
cout << endl;
} else
{
cout << "-1" << endl;
}
return 0;
}