拓扑排序

拓扑排序(所有点按照先后顺序排成序列)

  • 注:图必须是有向无环图

Kahn (卡恩) 算法

核心思想:用队列维护一个入度为0的节点的集合

具体代码如下:

vector<int> e[N], tp;
int din[N];

e[x] 用来存点 x 的邻点,tp 存拓扑序列,din[x] 用来存点x的入度

bool toposort() {
	queue<int> q;
	for(int i = 1; i <= n; i ++)
		if(!din[i]) q.push(i);  // 压入所有入度为0的点
	while(q.size()) {
		int x = q.front();
		q.pop();
		tp.push_back(x);
		for(auto y : e[x]) {
			if(--din[y] == 0) q.push(y);  // 将x的所有出边删除,若y的入度变为0,则将y入队
		}
	}
    return tp.size() == n;  // 如果最后tp中的元素不等于个数n,则说明有环
}

主函数操作:

int main() {
	cin >> n >> m;
	for(int i = 0; i < m; i ++) {
		cin >> a >> b;
		e[a].push_back(b);  // 压入a的邻点b
		din[b] ++;  // b的入度+1
	}
	if(!toposort()) cout << "-1" << endl;
	else {
		for(auto x : tp) {
			cout << x << " ";   // 遍历输出拓扑序
		}
	}
	return 0;
}

DFS算法

核心思想:染色法 每个点的颜色都会经历从 0 -> -1 -> 1 的染色过程

具体代码如下:

vector<int> e[N], tp;
int c[N]; // 染色数组

e[x] 用来存点 x 的邻点,tp 存拓扑序列,c[x] 存点 x 的颜色

bool dfs(int x) {
	c[x] = -1;  // -1 表示正在经历
	for(auto y : e[x]) {
		if(c[y] < 0) return 0;  // 为-1说明回到了祖先节点,有环退出
		else if(!c[y])  // 没走过继续走
			if(!dfs(y)) return 0;  // 有环退出
	}
	c[x] = 1;  // 1 表示已经完成
	tp.push_back(x);  // 完成的点加入tp数组
	return 1;
}

void toposort() {
	memset(c, 0, sizeof c);  // 先让每一个点都染色成0
	for(int i = 1; i <= n; i ++) {
		if(!c[i])  // 遍历每一个点,如果没走就dfs
			if(!dfs(i)) return ;  // 判断是否有环
	}
	reverse(tp.begin(), tp.end());  // dfs是从后往前记录的,所以需要反转下
}

重点

  • Kahn 算法是用队列维护, 顺着拓扑序收集点

  • DFS 算法是用系统栈维护, 逆着拓扑序收集点

  • 时间复杂度: \(O(E + V)\ 边数 + 点数\)

  • 不是连通也不影响拓扑排序

  • 若求字典序最小的拓扑排序,则将 Kahn 算法中的 队列 换成 优先队列(小根堆) 时间复杂度变为 \(O(E + VlogV)\)

点击查看代码
#include <bits/stdc++.h>

using namespace std;
const int N = 1e6 + 10;

int n, m, a, b;
vector<int> e[N], tp;
int din[N];

void toposort() {
	priority_queue<int, vector<int>, greater<int>> q;   // 小根堆
	for(int i = 1; i <= n; i ++)
		if(!din[i]) q.push(i);
	while(q.size()) {
		int x = q.top();
		q.pop();
		tp.push_back(x);
		for(auto y : e[x]) {
			if(--din[y] == 0) q.push(y);
		}
	}
}

int main() {
	cin >> n >> m;
	for(int i = 0; i < m; i ++) {
		cin >> a >> b;
		e[a].push_back(b);	// 压入a的邻点b
		din[b] ++;  // b的入度+1
	}
	toposort();
    for(auto x : tp) {
		cout << x << " ";	// 遍历输出拓扑序
    }
	return 0;
}
posted @ 2024-08-20 11:02  懒羊羊爱吃灰太狼  阅读(51)  评论(0)    收藏  举报