• 学习记录:拓扑排序


    学习记录:拓扑排序

    概念:

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

    有向无环图(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;
    }
    
  • 相关阅读:
    jquery在线手册
    bootstrap学习之路
    实用的cmd命令
    在源代码中插入防止盗版代码片段的方式
    常用的正则表达式
    仿站步骤
    thinkphp 公用函数
    php switch判断一个数所在的范围
    ps学习教程
    九度oj 题目1185:特殊排序
  • 原文地址:https://www.cnblogs.com/Salty-Fish/p/13417315.html
Copyright © 2020-2023  润新知