赞
踩
在看到“拓扑序列”这4个字的时候,笔者人是傻的。拓扑序列是啥?听着就感觉好厉害!然后,当我得知“拓扑”两个字其实是一个大家都知道的单词“Top”的音译时,我不禁在想:翻译成这样,估计是故意让大家觉得这个知识点很难的吧。事实证明的确是这样,拓扑排序的概念与实现都是非常简单的。别被看上去高大上的名字吓到了。
首先,我们给出一个如图的有向图
然后我们给出一个序列a={1,2,3,4},我们随便在有向图中选择一条边,我们都能满足在序列a中,这条边起点的编号一定在终点的编号之前。那么,我们就称为序列a是这个有向图的拓扑序列。
根据拓扑序列的概念,我们能知道:对于某些有向图而言,它的拓扑序列是不唯一的。只要是一个序列满足拓扑序列的定义,我们就可以说这个序列是这张图的拓扑序列。
当然,并不是所有图都是存在拓扑序列的,如果我们发现有向图中存在环,那么无论如何我们也找不出一个序列满足拓扑序列的定义。
如图,我们发现如果在之前的图中加上一条边,那么这张图就没有拓扑序列了。
首先,我们需要复习(预习?)一下离散数学中的知识点,入度。
所谓入度,就是对于有向图中的一个点而言,有多少个箭头是指向它的的。比如在最上方的没有环的有向图中,点1的入度为0,点2与3的入度都为1,点3的入度为2。
在了解了入度后,我们开始进行拓扑排序,在排序中,我们使用的是BFS广度优先搜索的思路进行点的遍历。
0.初始化:建图,求出所有点的入度。
1.我们首先遍历全图要找到入度为0的点,我们在我们的图中首先找到了点1
广度遍历所有以1为起点的边,删除这些边,操作为点2的入度减去1,然后就是这样,我们把点1放进拓扑序列中的最前面,a={1};
再次循环遍历所有还没有放进序列的点,我们发现图中点2的入度已经变成0了,标记出来重复对点1的操作,a={1,2}
继续遍历,我们找到了点3,操作。a={1,2,3}
最后找到了点4,所有点都操作完毕,a={1,2,3,4},拓扑排序结束。
之前我们说过,并不是所有的有向图都存在拓扑序列的,那么我们要怎么来进行判断呢?首先,我们放一个存在环的有向图来进行上述的算法
我们对于上述图按照之前的算法进行处理完点1,2后,会变成这样
此时,a={1,2},按照算法,我们应该在剩下的点中寻找入度为0的点,然后我们发现,找不到了,由于有环的存在,我们已经无法在接下来的图中找到入度为0的点了,算法结束,最后序列a定格在a={1,2}无法继续更新。
如果根据上面的算法判断,程序会认为此时的a就是最后的拓扑序列,但是显然不是。我们发现a中的点的数量为2,小于点的总数5。所以,假如我们求出最后的序列中点的数量少于点的总数,那么我们就知道有向图中存在环,该图不存在拓扑序列。
例题链接:Acwing 有向图的拓扑序列
给定一个 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≤1e5 输入样例: 3 3 1 2 2 3 1 3 输出样例: 1 2 3
这道题就是一道板子题,所以我们直接贴上代码,不过要看懂代码你需要事先知道以下知识点
1.c++STL中 数据结构queue的基本使用方法
2.链式前向星的存图方法----->链式前向星CSDN博客
3.作者重定义的memset函数( #define mem(a,b) memset(a,b,sizeof(a)) )
//#pragma GCC optimize(2) #include<iostream> #include<iomanip> #include<cstdio> #include<string> #include<algorithm> #include<cmath> #include<queue> #include<vector> #include<map> #include<stack> #include<set> #include<bitset> #include<ctime> #include<cstring> #include<list> #define ll long long #define ull unsigned long long #define INF 0x3f3f3f3f #define mem(a,b) memset(a,b,sizeof(a)) using namespace std; typedef pair<int, int> PII; const int N = 1e5 + 7; int n, m; int e[N], ne[N], h[N], id = 1; //链式前向星存图 int rd[N]; //存放每个点的入度 bool ch[N]; //用来判断该点有没有被放进序列中 bool haveans = true; //用来判断是否有拓扑序列 queue<int>q; //用来存放拓扑序列的队列 void add(int a, int b) //加边的函数 { e[id] = b; ne[id] = h[a]; h[a] = id++; rd[b]++; } void topsort() //拓扑排序实现函数 { bool flag = true; //flag为true就代表还能找到入度为0的点 while (flag) { flag = false; //先改为,找不到入度为0的点 for (int i = 1; i <= n; i++) { if (rd[i] == 0&&!ch[i]) { ch[i] = true; flag = true; //找到了入度为0的点就把flag改为true,继续循环 q.push(i); //把入度为0的点放进队列q for (int j = h[i]; j != -1; j = ne[j]) //BFS遍历所有以i为起点的边 { int k = e[j]; rd[k]--; //每一条边的入度减一,删除该边 } } } } } void solve() { bool flag = true; cin >> n >> m; mem(h, -1); for (int i = 0; i < m; i++) { int a, b; cin >> a >> b; add(a, b); } topsort(); if (q.size() != n) //如果队列中元素数量不等于点的总数就代表图中有环,没有拓扑序列 cout << "-1" << endl; else //否则输出拓扑序列 while (!q.empty()) { cout << q.front(); q.pop(); if (!q.empty()) cout << ' '; else cout << endl; } } int main() { //std::ios::sync_with_stdio(false); //cin.tie(0), cout.tie(0); solve(); return 0; }
作者:Avalon Demerzel,喜欢我的博客就点个赞吧,更多图论与数据结构知识点请见作者专栏《图论与数据结构》
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。