介绍

拓扑排序(Topological Order)是指,将一个有向无环图(Directed Acyclic Graph简称DAG)进行排序进而得到一个有序的线性序列。
如上图,排序结果可以为(a beg cf d),也可以为(a bcd eg f)。 e和c之间的大小关系无法比较,因此排序顺序也是不确定的。
常见的题型为:
- 拓扑+并查集 多节点算为图中一个节点
- 拓扑+动态规划 从后往前计算每个结点信息
- 拓扑判断环
算法
两个概念:
- 入度:指向结点的边的数量
- 出度:从结点出发的边的数量
算法逻辑:
- 找到所有入度为0的结点
- 将入度为0的结点及从结点出发的边移除并输出,重新计算这些结点的后继结点的入度
- 重复第二步,直到图中不存在入度为0的结点
- 输出的结点的顺序即为拓扑排序后的顺序。若输出结点的数量小于图中结点总数量,说明图中存在环
模板
力扣207 课程表 是一道经典拓扑排序的例题。利用拓扑排序判断图中是否有环
public class TopoLogicSort207 {
class Node {
List<Integer> nextNodeList; // 后继结点
int inDeg; // 入度
public Node () {
inDeg = 0;
nextNodeList = new ArrayList<>();
}
}
Node [] nodes;
private void init(int numCourses, int[][] prerequisites) {
nodes = new Node[numCourses];
for (int i = 0; i < numCourses; i++) {
nodes[i] = new Node();
}
for (int[] params : prerequisites) {
nodes[params[1]].nextNodeList.add(params[0]);
nodes[params[0]].inDeg++;
}
}
/**
* 拓扑排序,判断图中是否有环
* @param numCourses 结点数量
* @return 是否有环
*/
private boolean topoLogicSort(int numCourses) {
// 找到所有起始点(入度为0的结点)
Queue<Node> queue = new LinkedList<>();
for (int i = 0; i < numCourses; i++) {
if (nodes[i].inDeg == 0) {
queue.add(nodes[i]);
}
}
while (!queue.isEmpty()) {
// current也是输出结点的顺序
Node current = queue.poll();
numCourses--;
// 计算当前节点及边从图中移除后,后继结点的入度
for (Integer nextIndex : current.nextNodeList) {
// 当前节点移除后,所有后继结点入度减一
nodes[nextIndex].inDeg--;
// 取入度为0的结点进入队列
if (nodes[nextIndex].inDeg == 0) {
queue.add(nodes[nextIndex]);
}
}
}
// 如果图中存在环,则无法遍历所有结点,因此遍历后剩余结点数大于0
return numCourses == 0;
}
public boolean canFinish(int numCourses, int[][] prerequisites) {
// 初始化,构建图(邻接表)
init(numCourses, prerequisites);
// 拓扑排序
return topoLogicSort(numCourses);
}
public static void main(String[] args) {
TopoLogicSort207 object = new TopoLogicSort207();
System.out.println(object.canFinish(23, new int[][]{{1, 0}, {0, 1}, {1, 2}}));
}
}
例题
力扣207 课程表 并查集判断环
力扣851 喧闹与富有 并查集+动态规划
HDU1811Rank of Tetris 拓扑排序+并查集
17年写的代码,C++,正好作为这篇文章的例题
每个选手使用评级排名,相同评级用编号排名。问能不能正确得到排名名单。
- 能正常得到排名输出OK
- 排名出现环状冲突,则输出CONFLICT
- 假如无法比较两个选手的排名,则输出UNCERTAIN
根据选手关系建图,每个结点代表相同评级的选手集合(通过并查集生成),根据拓扑排序判断是否有环,计算入度为0结点个数来判断是否有选手之间无法排名。
#include<iostream>
#include<vector>
#include<string.h>
#include<queue>
using namespace std;
#define N 10000+7
#define mem(arr,a) memset(arr,a,sizeof(arr))
int par[N];
vector<int>G[N];
int indeg[N];
bool flag = false;
int n, m;
int k;
struct Edge{
int a, b; char c;
void set(int x, int y, char z){ a = x, b = y, c = z; }
};
Edge edge[2*N];
int find(int x){
if (par[x] == x)return x;
return par[x] = find(par[x]);
}
bool unite(int a, int b){
a = find(a), b = find(b);
if (a == b)return false;
par[a] = b;
return true;
}
void init(){
k = 0;
flag = false;
mem(indeg, 0);
for (int i = 0; i < n; i++)G[i].clear();
for (int i = 0; i < n; i++)par[i] = i;
}
void topo(){
queue<int>q;
for (int i = 0; i < n; i++){
if (par[i] == i&&indeg[i] == 0)q.push(i);
}
while (!q.empty()){
if (q.size() != 1)flag = true;
int x = q.front(); q.pop();
for (int i = 0; i < G[x].size(); i++){
indeg[G[x][i]]--;
if (indeg[G[x][i]] == 0){
q.push(G[x][i]);
}
}
k++;
}
if (k < n)cout << "CONFLICT" << endl;
else if (flag)cout << "UNCERTAIN" << endl;
else cout << "OK" << endl;
}
int main(){
while (cin >> n >> m){
init();
for (int i = 0; i < m; i++){
int a, b; char c;
cin >> a >> c >> b;
edge[i].set(a, b, c);
if (c == '='){
if (unite(a, b))k++;
}
}
for (int i = 0; i < m; i++){
Edge&temp = edge[i];
if (temp.c == '=')continue;
int x = find(temp.a);
int y = find(temp.b);
if (temp.c == '>'){
G[x].push_back(y);
indeg[y]++;
}
else {
G[y].push_back(x);
indeg[x]++;
}
}
topo();
}
}
280

被折叠的 条评论
为什么被折叠?



