减治法——拓扑排序

拓扑排序

一个常见的例子,我们考虑五门必修课的一个集合{C1.C2.C3,C4,C5},一个在校的学生必须在某个阶段修完这几门课程。可以按照任何次序学习这些课程,只要满足下面这些先决条件:C1和C2没有任何先决条件,修完C1和C2才能修C3,修完C3才能修C4,而修完C3和C4才能修C5。这个学生每个学期只能修一门课程。这个学生应该按照什么顺序来学习这些课程?
这种状况可以用一个图来建模,
在这里插入图片描述
它的节点代表课程,有向边表示先决条件。就这个图来说,上面这个问题其实就是:我们是否可以按照这种次序列出它的顶点,使得对于图中每一条边来说,边的起始顶点总是排在边的结束顶点之前。(大家是不是能够求出该图节点的这样一个序列呢?)
这个问题称为拓扑排序(topological sorting)。可以对任意一个有向图提出这个问题,但很容易发现,如果有向图具有一个有向的回路,该问题是无解的。因此,为了使得拓扑排序成为可能,问题中的图必须是一个无环有向图。其实,为了使拓扑排序成为可能,无环有向图不仅是必要条件,而且是充分条件。也就是说,如果一个图没有回路,对它来说,拓扑排序是有解的。而且,有两种高效的算法,它们既可以验证一个有向图是否是一个无环有向图,又可以在是的情况下,输出拓扑排序的一个顶点序列。

深度优先遍历简单应用

执行一次DFS遍历,并记住顶点变成死端(即退出遍历栈)的顺序。将该次序反过来就得到拓扑排序的一个解,当然,在遍历的时候不能遇到回边。如果遇到一条回边,该图就不是无环有向图,并且对它顶点的拓扑排序是不可能的。

Java代码实现

package com.算法.减治法;

import java.util.*;

/**
 * @Author Lanh
 **/
public class 拓扑排序 {
    public static void main(String[] args) {
        List<Vertex> Graph = new LinkedList<>();
        Vertex C1 = new Vertex("C1",new LinkedList<>());
        Vertex C2 = new Vertex("C2",new LinkedList<>());
        Vertex C3 = new Vertex("C3",new LinkedList<>());
        Vertex C4 = new Vertex("C4",new LinkedList<>());
        Vertex C5 = new Vertex("C5",new LinkedList<>());
        Graph.add(C1);Graph.add(C2);Graph.add(C3);Graph.add(C4);Graph.add(C5);

        //添加边
        C1.list.add(C3);
        C2.list.add(C3);
        C3.list.add(C4);
        C3.list.add(C5);
        C4.list.add(C5);

        DFS(Graph);

        Set<String> set = new HashSet<>(list);
        if (flat){ //没有重复元素,意味着没有环
            Collections.reverse(list);
            System.out.println("拓扑排序的一个解如下:");
            System.out.println(list);
        }else System.out.println("该有向图有环!");
    }

    static int count = 0;
    static List<String> list = new ArrayList<>();

    public static void DFS(List<Vertex> Graph){
        Graph.forEach(V -> {
            if (V.mark==0) dfs(V);
            while (!stack.isEmpty()){
                Vertex vertex = stack.pop();
                list.add(vertex.v);
            }
        });
    }

    static Stack<Vertex> stack = new Stack<>();
    static boolean flat = true;
    private static void dfs(Vertex vertex) {
        stack.push(vertex);
        vertex.mark = 1;
        if (hasNext(vertex)){
            vertex.list.forEach(V -> {
                if (V.mark==0) {
                    dfs(V);
                }else if (V.mark==1){
                    flat=false;
                }
            });
        }
        vertex.mark = 2;
    }

    public static boolean hasNext(Vertex vertex){
        if (vertex.list == null) return false;
        if (vertex.list.size() == 0) return false;
        for (int i=0;i<vertex.list.size();i++){
            if (vertex.list.get(i).mark==0) return true;
        }
        return false;
    }

    static class Vertex{
        String v;
        List<Vertex> list = null;
        int mark = 0;//0表示没访问,1表示访问中,2表示访问完成

        public Vertex(String v,List<Vertex> list){
            this.v = v;
            this.list = list;
        }
    }
}

结果

如果一个节点访问另一个正在访问的节点意味着,有环,即没有拓扑排序的解
按照反序将节点存进list,将list翻转即是拓扑排序的一个解:
在这里插入图片描述

减治法

第二种算法基于减(减一)治技术的一个直接实现:不断地做这样一件事,在余下的有向图中求出一个源(source),它是一个没有输入边的顶点,然后把它和所有从它出发的边都删除。(如果有多个这样的源,可以任意选择一个。如果这样的源不存在,算法停止,因为该问题是无解的)顶点被删除的次序就是拓扑排序问题的一个解。如下所示:

拓扑排序问题可能会有若干个不同的可选解

在这里插入图片描述

Java代码实现

package com.算法.减治法;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * @Author Lanh
 **/
public class 拓扑排序 {
    public static void main(String[] args) {
        List<Vertex> Graph = new LinkedList<>();
        Vertex C1 = new Vertex("C1",new LinkedList<>());
        Vertex C2 = new Vertex("C2",new LinkedList<>());
        Vertex C3 = new Vertex("C3",new LinkedList<>());
        Vertex C4 = new Vertex("C4",new LinkedList<>());
        Vertex C5 = new Vertex("C5",new LinkedList<>());
        Graph.add(C1);Graph.add(C2);Graph.add(C3);Graph.add(C4);Graph.add(C5);

        //添加边
        C1.list.add(C3);
        C2.list.add(C3);
        C3.list.add(C4);
        C3.list.add(C5);
        C4.list.add(C5);

        List<String> list = topologicalSort(Graph);
        System.out.println(list);
    }

    public static List<String> topologicalSort(List<Vertex> graph){
        List<String> list = new ArrayList<>();
        int[] ins = new int[graph.size()];
        for (int i = 0;i<graph.size();i++) ins[i] = 0;
        while (!graph.isEmpty()){
            graph.forEach(vertex -> vertex.list.forEach(v -> {
                ins[graph.indexOf(v)] = 1;
            }));
            int j = 0;
            while (ins[j]!=0) {
                j++;
                if (j>=graph.size()) return null;
            }
            list.add(graph.remove(j).v);
            for (int i = 0;i<graph.size();i++) ins[i] = 0;
        }
        return list;
    }

    static class Vertex{
        String v;
        List<Vertex> list = null;
        int mark = 0;//0表示没访问,1表示访问中,2表示访问完成

        public Vertex(String v,List<Vertex> list){
            this.v = v;
            this.list = list;
        }
    }
}

结果

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

绿豆蛙给生活加点甜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值