拓扑排序


概念

拓扑排序

拓扑排序是一种在DAG上进行的算法。拓扑排序可以解决有若干对关系,需要求出满足所有关系的一种方案/最大值/最小值的问题。拓扑排序可以求出图中的一条拓扑序列。

拓扑排序可以检查出有向图中是否存在环,如果一个有向图存在合法的拓扑序列,说明该有向图内无环;反之,说明该有向图中有环。拓扑排序是无法给有向带环图求出拓扑序列的。

拓扑排序的时间复杂度是\(O(N + M)\),其中\(N\)为点数,\(M\)为边数。

拓扑序列

有若干条有向边(u, v),要求所有的u均在线性序列中出现在对应的v前,这样的序列称为拓扑序列。
形象地说,有若干件事情要做,做事件A前必须先完成事件B,做事件C前必须先完成事件D……拓扑序列就是做这些事情的顺序。

算法思路

核心思路

我们可以将一条有向图看做是一种约束关系,每个点的入度就是这个点被约束的次数。如果一个点被约束,则其不可以加入拓扑序列。

对于入度为0的点uu此时没有约束,则其可以加入拓扑序列中。同时,因为u已经加入拓扑序列,其带来的约束会消失。即删除每条u连出的有向边(u, v)(也就是删除约束关系),同时v的入度会-1

对于一个点v来说,若其入度不为0,说明v还有对应的u在约束,所以v不能加入拓扑序列;若其入度为0,说明v所有对应的u均已加入拓扑序列,所以v可以加入拓扑序列。因此,拓扑排序可以分为三步:

  1. 找出入度为0的点u,并将其加入拓扑序列。
  2. 删除u连出的每条有向边(u, v)
  3. 重复第一步,直到图中没有入度为0的点为止。

算法结束后,如果加入拓扑序列的顶点数 < 图中所有的顶点数,说明有向图中存在环;否则,拓扑排序已经求出了该图的一条拓扑序列。

如果需要求出字典序最小的拓扑序列,则存储的数据结构需要换成小根堆的优先队列。

建模思路

如果题目中给出了若干条约束关系:\(A\)必须与\(B\)……\(C\)必须与\(D\)……则这道题目大概率可以使用拓扑排序解决。使用拓扑排序的关键,在于正确的建图
我们可以把一条有向边看做是一种关系。例如,如果u的点权 < v的点权,我们可以将其看做是一条有向边(u, v)。又如,\(NOIP 2013\)\(T4\)车站分级一题中,我们将一条有向边(u, v)看做是车站u的级别 < 车站v的级别。
建图后,拓扑排序的解法就很容易得出了。

拓扑排序非常适合与\(\textbf{DAGdp}\)进行搭配使用。因为进行状态转移的时候,要求其的前驱状态必须都已经计算完毕,与拓扑序列的性质不谋而合。因此,拓扑排序和记忆化搜索是实现\(DAGdp\)的最佳选择。

对于一个有向图\(G\),如果在拓扑排序的过程中,队列里出现了多个元素,说明图\(G\)的拓扑序不唯一,反之说明图\(G\)的拓扑序是唯一的。因为当一个元素入队后,其所有的前驱一定都已经出队。如果此时队列里有多个元素,则这些元素没有前驱的约束,在之后的拓扑序中的位置可以安排。

拓扑排序有两个非常重要的性质:一,假如在拓扑排序的过程中 ,队列里出现了多于一个元素,说明该图的拓扑序不唯一;二,假如拓扑序列的长度小于图中的顶点数量,说明图中有环。这两个性质对解题有极大的作用。

模板

#include <cstdio>
#include <queue>
using namespace std;
#define maxn 1005
#define maxm 100005

struct node
{
	int to, nxt;
}edge[maxm];

int n, m, cnt;
int head[maxn], indeg[maxn], res[maxn];
queue<int> q;

void topo()
{
	for (int i = 1; i <= n; i++)
		if (!indeg[i])
			q.push(i);
	while (!q.empty())
	{
		int v = q.front();
		q.pop();
		res[++cnt] = v;
		for (int i = head[v]; i; i = edge[i].nxt)
		{
			indeg[edge[i].to]--;
			if (!indeg[edge[i].to]) 
				q.push(edge[i].to);
		}
	}
}

int main()
{
	int x, y;
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i++)
	{
		scanf("%d%d", &x, &y);
		edge[i].to = y;
		edge[i].nxt = head[x];
		head[x] = i;
		indeg[y]++;
	}
	topo();
	for (int i = 1; i <= cnt; i++)
		printf("%d ", res[i]);
	printf("\n");
	return 0;
}

例题选讲

最长链问题

给定一个有向无环图\(G\),试求\(G\)中最长链的长度。

这道题是一个经典的\(DAGdp\)问题。令\(dp_{i}\)表示以顶点\(i\)结尾的最长链的长度。我们在拓扑排序的时候可以顺便进行状态转移。因为如果拓扑排序的过程中,某个顶点\(u\)入度已经为\(0\),说明与\(u\)相连的顶点的\(dp\)值都已经计算完毕。此时,以\(u\)结尾的最长链的长度等于所有相邻顶点的最长链长度加一,即\(dp_{u} = max(dp_{v} + 1), (u, v) \in E\)。最终取最大值就是该有向无环图的最长链长度。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM