拓扑排序

拓扑排序
先穿袜子才能穿鞋子:袜子->鞋子
秋裤->裤子 裤子->鞋子 秋衣->外套 
整体穿衣顺序:
秋衣->外套->秋裤->裤子->袜子->鞋子 或
秋裤->裤子->袜子->鞋子-->秋衣->外套

问题 --> 数据结构 --> 算法

拓扑排序算法:
如果s需要先于t执行,那就添加一条s指向t的边。
1 Kahn算法:如果某个节点的入度为零,则说明没有节点需要先于该节点执行,可以直接执行,并删除该节点。所以解决步骤为:
1)统计每个节点及其入度,即边指向该节点的次数
2)删除入度为0的节点,同时删除该节点导致的入度
3)如节点1和2关系为:1<->2,两节点入度均为1,不存在入度为0的节点。

2 DFS算法的变形解决
假设存在,s->t,a->t;需要的拓扑排序结果是sat或ast
正常的DFS是先获取顶点,然后根据顶点遍历指向的节点。即访问顺序是sta或ats。
所以我们采用遍历逆邻接表的方式:
1)s->t,a->t 转化为 t->s,t->a
2) 访问的方式也需要修改,一定是先访问节点t指向的节点s和a,然后再访问t
此时的结果是sat或ast
好麻烦,还是采用kahn算法吧。根据入度,很清晰,同时解决环的问题

邻接表:顶点值,顶点对应的链表中存储顶点指向的点的值
逆邻接表:顶点值,顶点对应的链表中存储指向该顶点的值


判断图是否有环
首先我们介绍一个对于无向图和有向图通用的算法,先讲算法思路:
  1.统计各个图中各个点的入度数(能够到达这个点的点的数量)。
  2.然后找出入度数为0的点(无向图找入度数为1的点)。
  3.删除入度数为0的点,将其边也删除。
  4.重复2,直到所有点入度都为0,则为无环图,如果找不到入度为0的点,则为有环图。
参考:https://www.cnblogs.com/cmai/p/7517729.html

public class TopologicalSort {


    public static void main(String[] args) {

        LinkedList<Integer>[] list = new LinkedList[7];
        LinkedList list0 = new LinkedList<>();
        LinkedList list1 = new LinkedList<>();
        list0.add(1);
        list1.add(2);
        LinkedList list2 = new LinkedList<>();
        LinkedList list3 = new LinkedList<>();
        list2.add(3);
        list2.add(6);
        LinkedList list4 = new LinkedList<>();
        LinkedList list5 = new LinkedList<>();
        list3.add(5);
        list4.add(5);
        LinkedList list6 = new LinkedList<>();
        list6.add(4);

        list[0]=list0;
        list[1]=list1;
        list[2]=list2;
        list[3]=list3;
        list[4]=list4;
        list[5]=list5;
        list[6]=list6;
        Graphic graph = new Graphic(7, list);
        kahn(graph);

        boolean[] vistied = new boolean[graph.getNodeNum()];
        reverseGraph(graph);
        dfsGraph(graph, vistied);

    }

    // 对图进行拓扑排序,同时判断是否有环
    // 1 统计每个节点的入度,即边指向该节点的次数
    // 2 删除入度为0的节点,同时删除该节点导致的入度
    // 3 如果存在有节点,而且所有节点入度不为0,那一定是有环,
    // 如节点1和2关系为:1<->2,两节点入度均为1,不存在入度为0的节点。
    public static void kahn(Graphic g){
        int num = g.getNodeNum();
        LinkedList<Integer>[] list = g.getList();
        // 用于存储节点入度,index为节点的值,value为入度
        int[] enter = new int[num];

        // 统计节点入度
        // 某个节点的指向
        LinkedList<Integer> nodeList ;
        LinkedList<Integer> nodes =  new LinkedList();
        for(int i=0; i<num; i++){
            // 记录所有顶点
            nodes.addLast(i);
            nodeList = list[i];
            for(int j=0; j<nodeList.size(); j++){
                enter[nodeList.get(j)]++;
            }
        }
        // 假设有环,每次循环,如果存在入度为0的节点,即有节点没有被指向,说明整体不是环
        boolean circle = true;
        // 删除入度为0的节点
        while(nodes.size()>0){
            circle = true;
            // 遍历入度为0的直接移除
            for(int i =0; i<num; i++){
                if(enter[i] == 0){
                    // 如果存在入度为0的节点,即有节点没有被指向,说明整体不是环
                    circle = false;
                    System.out.print("-->"+i);
                    nodes.remove(new Integer(i));
                    nodeList = list[i];
                    // 处理被移除节点
                    for(int j=0; j<nodeList.size(); j++){
                        enter[nodeList.get(j)]--;
                    }
                    // 这里代表移除
                    enter[i]--;
                }
            }
            if(circle){
                System.out.println("有环,失败。。。。");
                break;
            }
        }
        System.out.println();
    }



    // 与一般的深度搜索不同,这里要求要打印当前节点,必须先打印该节点指向的节点
    public static void dfsGraph(Graphic g, boolean[] vistied) {
        int nodeNum = g.getNodeNum();
        LinkedList[] list = g.getList();
        // 所有图的顶点都需要访问到,因为可能存在节点独立的场景
        for(int i=0; i<nodeNum; i++){
            // i节点可能因为访问i之前的节点已经被访问到,所以需要判断
            if(!vistied[i]){
                dfsNode(i, list, vistied);
            }
        }
    }

    // 指向顶点指向的所有节点都要提前访问
    private static void dfsNode(int node, LinkedList<Integer>[] list, boolean[] vistied) {
        // 指向node的所有节点都需要提前访问,所以这里遍历所有
        for(int j=0; j<list[node].size(); j++){
            int temp = list[node].get(j);
            if(vistied[temp] == true){
                continue;
            }
            dfsNode(temp, list, vistied);
        }
        vistied[node] =true;
        System.out.print("-->"+node);
    }


    /**
     * desc:从邻接表转化为逆向邻接表,即原本1—>2,执行后:2->1
     */
    public static void reverseGraph(Graphic g){
        LinkedList<Integer>[] list = g.getList();
        int nodeNum = g.getNodeNum();

        LinkedList<Integer>[] newLists = new LinkedList[nodeNum];
        for(int i=0; i<nodeNum; i++){
            newLists[i] = new LinkedList<>();
        }

        for(int i=0; i<nodeNum; i++){
            LinkedList<Integer> integers = list[i];
            for(int j=0; j<integers.size(); j++){
                newLists[integers.get(j)].add(i);
            }
        }
        g.setList(newLists);
    }


    static class Graphic{
        int nodeNum;
        LinkedList<Integer>[] list;

        public Graphic(int nodeNum, LinkedList<Integer>[] list){
            this.nodeNum = nodeNum;
            this.list = list;
        }

        public int getNodeNum() {
            return nodeNum;
        }

        public void setNodeNum(int nodeNum) {
            this.nodeNum = nodeNum;
        }

        public LinkedList[] getList() {
            return list;
        }

        public void setList(LinkedList[] list) {
            this.list = list;
        }
    }
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值