原文链接:图论第四讲:拓扑排序
说明:CSDN和公众号文章同步发布,需要第一时间收到最新内容,请关注公众号【比特正传】。
之前的图论合集文章中讲了图的存储遍历、最短路等算法,文章链接如下
图论第一讲:图的存储及遍历
图论第二讲:Dijkstra求最短路
图论第三讲:Bellman-ford与SPFA求最短路
今天来梳理一篇关于拓扑排序的文章。
0、其他相关概念
入度:在有向图中,指向自己的箭头的个数。
1、拓扑排序基本概念
简单点说,在有向图中,任意一对相连节点u指向v,那么排序后u一定会在v的前面,得到这样的序列的算法称为拓扑排序。
上面的说法很简陋,但是也很容易理解,如果你去查百度百科,我相信对于大部分初学者来说,看完可能就退出这篇文章了。
2、问题描述
比如一道拓扑排序的模板题,先看题
理解了这道题的意思,就很容易理解拓扑排序是什么意思了。
题目链接:
https://www.luogu.com.cn/problem/B3644
3、算法描述
拓扑排序应该算是图论中比较简单的一个算法,首先我们先思考一下,在有向图中,如果当前一个节点的入度为0,则可以认为该节点是当前图中“辈分”最大的节点。那么对当前图拓扑排序的时候,一定会先输出该节点。
有了以上共识,我们可以借助入度来实现拓扑排序。
Step1:找出当前图论中未被标记的所有入度为0的节点,加入普通队列中,这些节点的顺序就已经排好了,可以当前输出,因此需要更新他们相邻节点的入度。
Step2:针对Step1中找出的节点,依次访问这些节点的相邻结点,并将其相邻结点的入度-1(相当于删除了当前入度为0的点,所以相邻结点的入度会-1),然后判断相邻结点的入度,如果-1后等于0,那么将其加入队列。
循环上面的步骤,直到队列为空即可。
4、code
看完算法描述,结合代码浏览更容易理解
#include "bits/stdc++.h"
using namespace std;
const int N = 1e5+7;
int n, m, u, v, in[N];
queue<int> q;
vector<int> g[N];void topsort() {for(int i=1; i<=n; i++) {if(in[i] == 0) q.push(i); // 初始的时候,找到所有入度为0的节点,加入队列 }while(!q.empty()) {int head = q.front(); q.pop(); // 取队首并弹出,一般都是同步操作 cout << head << " "; // 输出序列中当前的节点 for(int x : g[head]) { // 找与其相邻的其他结点 in[x]--; // 入度-1 if(in[x] == 0) q.push(x); // 如果相邻结点的入度为0,那么加入队列 }}return;
}int main(){scanf("%d", &n);for(u=1; u<=n; ++u) {while(true) {scanf("%d", &v);if(v == 0) break; // 根据题目意思,遇到0停止 g[u].push_back(v);in[v]++; // 统计入度 }}topsort();return 0;
}
这道模板题呀,很简单,由于题目没有说,因此不需要考虑一些异常情况,比如图的连通性,如果在拓扑排序中有冲突怎么办等等,因此我们只需要考虑它的正常情况就行了,非常适合新手练习拓扑排序,自己动手完成吧。