拓扑排序

拓扑排序

拓扑排序常用来确定一个依赖关系集中,事物发生的顺序。拓扑排序是对有向无环图的顶点的一种排序,它使得如果存在一条从顶点A到顶点B的路径,那么在排序中B出现在A的后面。
简单说就是一些事件,有先后依赖关系

  1. 大学课程安排,某些课是必须有前驱课程的。
    例如:高等数学学完才可以学大学物理
    c语言学完才可以学数据结构

  2. 又或者是建筑工程,地基打完才可以盖楼,盖楼后才可以装修

    具有这些关系的对象如何安排这些事件就要用到这个知识 拓扑排序

    我们把这种顶点表示活动、边表示活动间先后关系的有向图称做顶点活动网(Activity On Vertex network),简称AOV网。
    详细定义:http://baike.sogou.com/v1486429.htm?fromTitle=%E6%8B%93%E6%89%91%E6%8E%92%E5%BA%8F

    拓扑排序模板

    有向无环图才可以使用

import java.util.ArrayList;
//值,入度出度,点集边集
class Node {
    public int value;
    public int in;
    public int out;
    public ArrayList<Node> nexts;
    public ArrayList<Edge> edges;

    public Node(int value) {
        this.value = value;
        in = 0;
        out = 0;
        nexts = new ArrayList<>();
        edges = new ArrayList<>();
    }
}
//权重 连接哪两个点
public class Edge {
    public int weight;
    public Node from;
    public Node to;

    public Edge(int weight, Node from, Node to) {
        this.weight = weight;
        this.from = from;
        this.to = to;
    }

}
public class Graph {
    public HashMap<Integer,Node> nodes;
    public HashSet<Edge> edges;

    public Graph() {
        nodes = new HashMap<>();
        edges = new HashSet<>();
    }
}
public class GraphGenerator {
    public static void main(String[] args) {
        //input arr[][];
        Graph graph=createGraph(arr);
        ArrayList<Node> result=sortedTopology(graph);
    }
    public static Graph createGraph(Integer[][] matrix) {
        Graph graph = new Graph();
        for (int i = 0; i < matrix.length; i++) {
            Integer from = matrix[i][0];
            Integer to = matrix[i][1];
            Integer weight = matrix[i][2];
            if (!graph.nodes.containsKey(from)) {
                graph.nodes.put(from, new Node(from));
            }
            if (!graph.nodes.containsKey(to)) {
                graph.nodes.put(to, new Node(to));
            }
            Node fromNode = graph.nodes.get(from);
            Node toNode = graph.nodes.get(to);
            Edge newEdge = new Edge( fromNode, toNode,weight);
            fromNode.nexts.add(toNode);
            fromNode.out++;
            toNode.in++;
            fromNode.edges.add(newEdge);
            graph.edges.add(newEdge);
        }
        return graph;
    }
    // directed graph and no loop 有向无环图
    public static List<Node> sortedTopology(Graph graph) {
        HashMap<Node, Integer> inMap = new HashMap<>();//拿到所有点和点的入度
        Queue<Node> zeroInQueue = new LinkedList<>();
        for (Node node : graph.nodes.values()) {//遍历图中所有的点
            inMap.put(node, node.in);
            if (node.in == 0) {//入度为0就加入一个队列
                zeroInQueue.add(node);
            }
        }
        List<Node> result = new ArrayList<>();
        //生成拓扑排序的过程
        while (!zeroInQueue.isEmpty()) {
            Node cur = zeroInQueue.poll();//从0入度的节点里拿出一个节点
            result.add(cur);//加入拓扑排序的结果里
            for (Node next : cur.nexts) {//遍历这个节点的所有下级节点
                inMap.put(next, inMap.get(next) - 1);//将它连接到的节点的入度都-1(相当于把当前节点删除,那么与它相连的节点的入度-1,就会新出现入度为0的点,以为新节点之前所需要的依赖已经完成了)
                if (inMap.get(next) == 0) {//将新出现的入度为0的点加入队列,重复这个过程
                    zeroInQueue.add(next);
                }
            }
        }
        return result;
    }
}

Leagal or Not

题目描述:
ACM-DIY is a large QQ group where many excellent acmers get together. It is so harmonious that just like a big family. Every day,many “holy cows” like HH, hh, AC, ZT, lcc, BF, Qinz and so on chat on-line to exchange their ideas. When someone has questions, many warm-hearted cows like Lost will come to help. Then the one being helped will call Lost “master”, and Lost will have a nice “prentice”. By and by, there are many pairs of “master and prentice”. But then problem occurs: there are too many masters and too many prentices, how can we know whether it is legal or not?We all know a master can have many prentices and a prentice may have a lot of masters too, it’s legal. Nevertheless,some cows are not so honest, they hold illegal relationship. Take HH and 3xian for instant, HH is 3xian’s master and, at the same time, 3xian is HH’s master,which is quite illegal! To avoid this,please help us to judge whether their relationship is legal or not. Please note that the “master and prentice” relation is transitive. It means that if A is B’s master ans B is C’s master, then A is C’s master.
输入:
The input consists of several test cases. For each case, the first line contains two integers, N (members to be tested) and M (relationships to be tested)(2 <= N, M <= 100). Then M lines follow, each contains a pair of (x, y) which means x is y’s master and y is x’s prentice. The input is terminated by N = 0.TO MAKE IT SIMPLE, we give every one a number (0, 1, 2,…, N-1). We use their numbers instead of their names.
输出:
For each test case, print in one line the judgement of the messy relationship.If it is legal, output “YES”, otherwise “NO”.
样例输入:
3 2
0 1
1 2
2 2
0 1
1 0
0 0
样例输出:
YES
NO

该题大意为,在一个qq群里有着许多师徒关系,如果A是B的师傅,同时B是A的徒弟,一个师傅可能有许多徒弟,一个徒弟也可能有许多不同的师傅,输入该群所有的师徒关系,问是否存在这样一个非法情况:以三个人为例,即A是B的师傅,B是C的师傅,C反过来又是A的师傅。若我们将该群里的所有人都抽象为图上的节点。将所有的师徒关系都抽象成有向边(师傅指向徒弟),该实际问题就会转换成一个数学问题——该图是否存在一个环,即判断图是否为有向无环图。
无论如何,当需要判断某个图是否属于有向无环图的时候我们都要想到拓扑排序。若一个图存在符合拓扑排序的节点序列,则该图为有向无环图,反之非有向无环图。也就是说在这张图上拓扑排序成功,那么为有向无环图,拓扑排序不成功,则不是有向无环图。

//多输入输出
#include <iostream>  
#include <cstdio>  
#include <vector>  
#include <queue>  
using namespace std;  
vector<int> edge[501];  //邻接链表,因为边不存在权值,只需要保存与其邻接的节点编号即可
queue<int> Q;  //保存入度为0的节点的队列
int inDegree[101];  //统计每个节点的入度
int main()  
{  
    int n,m,a,b;  
    while(cin>>n>>m&&n) {  
        //init Node Information
        for(int i=0;i<n;i++) {  
            edge[i].clear(); //清空邻接链表 
            inDegree[i]=0;  //入度都为0
        }  
        while(m--){  
            cin>>a>>b;          
            inDegree[b]++;  //读边,累加入度
            edge[a].push_back(b);  //将b加入a的邻接链表
        }  
        while(Q.empty()==false){  
            Q.pop();  //清空队列(可能为上一组遗留数据,单输入输出不用考虑这个)
        }  
        for(int i=0;i<n;i++) {  //统计所有节点的入度
            if(inDegree[i]==0)  {//入度为0就加入队列
                     Q.push(i);  
                }           
        }  
        int cnt=0;  //计数器,统计已经确定的拓扑排序的节点个数
        while(Q.empty()==false){  //当队列中入度为0的节点未被取完时重复操作
            int newp=Q.front();  //读出队头节点编号,本题不需要求出确定的拓扑排序序列,故不做处理,若要求求出确定的拓扑排序次序,则将该节点紧接着放在已经确定的拓扑排序之后
            Q.pop();  //弹出队头元素
            cnt++;  //已确定拓扑排序节点个数+1
            for(int j=0;j<edge[newp].size();j++) {  //将节点以及其为弧尾的所有边去除
                inDegree[edge[newp][j]]--;  //去除某条边后,该边所指后继节点的入度-1
                if(inDegree[edge[newp][j]]==0)  {  
                    Q.push(edge[newp][j]);  //将新生成的入度为0的节点加入队列
                }  
            }  
        }  
        if(cnt==n) //所有节点都能被确认拓扑排序
            cout<<"YES"<<endl;  
        else 
            cout<<"NO"<<endl;  
    }  
    return 0;  
}  

该代码所有的节点至多进入队列一次,但每个节点取出时我们都要遍历以其为弧尾的边,故时间复杂度为O(N+E),N为节点个数,E为边的个数。

如本题所示,很多涉及图的问题,都没有给出直接的图结构,而是把图隐藏在一个实际问题当中,需要做题者自己讲实际问题抽象成一个图论问题,而这恰恰是所有图论题中的难点,这种把实际问题抽象出来的能力,需要读者在大量训练中慢慢总结。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值