学习记录:拓扑排序

学习记录:拓扑排序

概念:

拓扑排序是对有向无环图顶点的一种排序。

有向无环图(Directed Acyclic Graph)(DAG):

从任意顶点出发无法返回到出发点,即为有向无环图

它使得如果存在一条从\(v_i\)\(v_j\)的路径,那么在排序中\(v_j\)出现在\(v_i\)的后面。

可以这样想,看成一张技能树。只有在点好了低级技能后,才能点高阶技能。这样想可以很容易明白接下来拓扑排序的两个特征。

  1. 在图含有圈的情况下,拓扑排序是不可能的。
    如果技能树是个圈的话,高级技能需要低级技能作为前提,反之亦然,这就成死循环了。
  2. 拓扑排序不是唯一的。
    只要能点完技能树,那么每一种都是拓扑排序。要是点的先后顺序唯一,那就没有各种build可言了

a8Uyod.jpg

(搞张老滚5的图~)

代码实现

Kahn 算法

初步实现
  1. 先找出任意一个没有进入边的顶点,并打印该点。
  2. 删除1中顶点的所有出边,重复1,2直到全部打印完成。
  3. 最终打印顺序即为拓扑排序的结果

要注意的是,如果最后不存在没有进入边的顶点,但是还存在边,那么这个图一定是有环图

//《数据结构与算法分析——C语言描述》中的伪代码,稍微改了一下
void Topsort(Graph G) //给一张图
{
	Vertex V, W;						 //定义两个顶点
	for (int i = 0; i < Num_Vertex; i++) //遍历 顶点数 遍
	{
		V = FindVertex(); //找到入度为0且尚未排好序的顶点
		if (!V)			  //如果这样的点不存在,那么存在环
		{
			printf("Graph has a cycle");
			return;
		}
		Topnum[V] = i; //给定拓扑编号
		for each W adjacent to V //对于任何(V_v,V_w)而言,W的入度-1
				Indegres[W]--;
	}
}

这样最简单的思想直接写出来的代码,FindVertex是对Indegres(入度数组)的一次遍历,调用花费\(O(V)\)的时间,并且有\(V\)次调用,总的时间复杂度是\(O(V^2)\)

改进版

如果此图是稀疏图,那么FindVertex中的遍历大部分是无效的。

我们可以维护一个集合——未排序且入度为0的顶点,那么FindVertex函数只需要在此集合中删除一个点即可。在最后降低入度的循环中,检查每一个相邻的顶点,在入度为0时,加入到集合中。

为了实现这个集合,可以选用栈,队列,都行。毕竟排序结果不唯一。

void Topsort(Graph G)
{
	queue<vertex> q;//储存入度为0的队列
	vertex V, W;	//两个临时顶点
	for (each vertex in G)//对于G中的每个顶点
		if (Indegree(V) == 0)//入度为0
			Q.push(V);//入队
	int Count = 0;
	while (!q.empty())
	{
		V = q.front();
		q.pop();
		TopNum[V] = ++Count;//排序
		for each W adjacent to V//相邻的顶点
			if (--Indegree[W]==0)//入度为0
				q.push(W);//入队
	}
	if (count!=NumVerts)//并不是所有顶点都排序时
		printf("Graph has a cycle");
}

使用邻接表时,该算法的复杂度降至\(O(E+V)\)

拓扑排序模板题

代码:(这oj有毛病...)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

int mod = 9973;
const int INF = 1 << 28;
const int maxn = 1e2 + 10;
int main()
{
	int n, m;
	int in[maxn];
	vector<vector<int>> G;
	while (cin >> n >> m && n)
	{
		G = vector<vector<int>>(n + 1);
		memset(in, 0, sizeof(in));
		for (int i = 0, a, b; i < m; i++)
		{
			cin >> a >> b;
			in[b]++;
			G[a].push_back(b);
		}
		queue<int> q;
		int v;
		for (int i = 1; i <= n; i++)
			if (in[i] == 0)
				q.push(i);
		while (!q.empty())
		{
			v = q.front(), q.pop();
			cout << v << ' ';
			for (int i = 0; i < G[v].size(); i++)
				if (--in[G[v][i]] == 0)
					q.push(G[v][i]);
		}
		cout << endl;
	}
	return 0;
}
posted @ 2020-08-01 23:51  Salty_Fish  阅读(186)  评论(0编辑  收藏  举报