基于有向图的拓扑排序(Java实现)

1 拓扑排序的概念及推导

1.1 引入

在生活中,很多事情都讲求循序渐进。以学习java为例,需要从java基础,到jsp/servlet,到ssm,再到springboot等,是一个循序渐进且有依赖的过程。

在这里插入图片描述
为了简化问题,我们使用整数为顶点编号的标准模型来表示这个案例:
在这里插入图片描述
要学习这些课程,就需要指定一个循序渐进的方案,即对图中的顶点进行排序,将其转换为一个线性序列,这时就需要用到一种叫拓扑排序的算法。

1.2 概念

拓扑排序:给定一幅有向图,将所有的顶点排序,使得所有的有向边均从排在前面的元素指向排在后面的元素;此时就可以明确地表示出每个顶点的优先级。

在这里插入图片描述

1.3 检测有向图中的环

如果学校x课程前必须先学习y课程,学习y课程前必须先学习z课程,学习z课程前必须先学习x课程,如此就没有办法学习了,因为三个条件都不能同时满足,x,y,z组成了一个环:

在这里插入图片描述

因此,如果要使用拓扑排序解决优先级问题,首先要保证图中没有环的存在。

检测有向环的API设计

  • private boolean[] marked: 索引代表顶点,值表示当前顶点是否已经被搜索;
  • private boolean[] onStack:索引代表顶点,使用栈的思想,记录当前顶点有没有已经处于正在搜索的有向路径上;

当深度优先搜索时:

  1. 如果当前顶点正在搜索,则把对应的onStack数组中的值改为true,标识进栈;
  2. 如果当前顶点搜索完毕,则把对应的onStack数组中的值改为false,标识出栈;
  3. 如果即将要搜索某个顶点,但该顶点已经在栈中,则图中有环;

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

public class Digraph {
    //顶点数目
    private final int V;
    //边的数目
    private int E;
    //邻接表
    private Deque<Integer>[] adj;

    public Digraph(int V){
        //初始化顶点数量
        this.V = V;
        //初始化边的数量
        this.E = 0;
        //初始化邻接表
        this.adj = new Deque[V];
        for (int i = 0; i < adj.length; i++) {
            adj[i] = new LinkedList<>();
        }

    }

    //获取顶点数目
    public int V(){
        return V;
    }

    //获取边的数目
    public int E(){
        return E;
    }

    //向有向图中添加一条边 v->w
    public void addEdge(int v, int w) {
        //只需要让顶点w出现在顶点v的邻接表中,因为边是有方向的,最终,顶点v的邻接表中存储的相邻顶点的含义是: v->其他顶点
        adj[v].offer(w);
        E++;
    }

    //获取由v指出的边所连接的所有顶点
    public Deque<Integer> adj(int v){
        return adj[v];
    }

    //该图的反向图
    private Digraph reverse(){
        //创建有向图对象
        Digraph r = new Digraph(V);
        for (int v=0; v<V; v++){
            //获取由该顶点v指出的所有边
            for (Integer w : adj[v]) {//原图中表示的是由顶点v->w的边
                r.addEdge(w,v);//w->v
            }
        }
        return r;
    }
}
public class DirectedCycle {
    //索引代表顶点,值表示当前顶点是否已经被搜索
    private boolean[] marked;
    //记录图中是否有环
    private boolean hasCycle;
    //索引代表顶点,使用栈的思想,记录当前顶点有没有已经处于正在搜索的有向路径上
    private boolean[] onStack;

    //创建一个检测环对象,检测图G中是否有环
    public DirectedCycle(Digraph G){
        //初始化marked数组
        this.marked = new boolean[G.V()];
        //初始化hasCycle
        this.hasCycle = false;
        //初始化onStack数组
        this.onStack = new boolean[G.V()];

        //找到图中每一个顶点,让每一个顶点作为入口,调用一次dfs进行搜索
        for (int v =0; v<G.V();v++){
            //判断如果当前顶点还没有搜索过,则调用dfs进行搜索
            if (!marked[v]){
                dfs(G,v);
            }
        }
    }

    //基于深度优先搜索,检测图G中是否有环
    private void dfs(Digraph G, int v){
        //把顶点v表示为已搜索
        marked[v] = true;
        //把当前顶点进栈
        onStack[v] = true;
        //进行深度搜索
        for (Integer w : G.adj(v)) {
            //判断如果当前顶点w没有被搜索过,则继续递归调用dfs方法完成深度优先搜索
            if (!marked[w]) dfs(G,w);
            //判断当前顶点w是否已经在栈中,如果已经在栈中,证明当前顶点之前处于正在搜索的状态,那么现在又要搜索一次,证明检测到环了
            if (onStack[w]){
                hasCycle = true;
                return;
            }
        }
        //把当前顶点出栈
        onStack[v] = false;
    }

    //判断当前有向图G中是否有环
    public boolean hasCycle(){
        return hasCycle;
    }
}
public class Test {
    public static void main(String[] args) {
        Digraph g = new Digraph(5);
        g.addEdge(3,0);
        g.addEdge(1,0);
        g.addEdge(0,2);
        g.addEdge(2,1);
        g.addEdge(1,4);

        DirectedCycle cycle = new DirectedCycle(g);
        System.out.println(cycle.hasCycle());
    }
}
true

1.4 基于深度优先的顶点排序

添加一个栈reversePost用来存储顶点,当进行深度优先搜索时,每搜索完毕一个顶点,就把该顶点放入到reversePost中,这样就实现了顶点排序。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

public class DepthFirstOrder {
    //索引代表顶点,值表示当前顶点是否已经被搜索
    private boolean[] marked;
    //使用栈,存储顶点序列
    private Deque<Integer> reversePost;
    
	//创建一个基于深度优先的顶点排序对象,对图G进行顶点排序
    public DepthFirstOrder(Digraph G){
        //初始化marked数组
        this.marked = new boolean[G.V()];
        //初始化reversePost栈
        this.reversePost = new LinkedList<>();
        //遍历图中的每一个顶点,让每个顶点作为入口,完成一次深度优先搜索
        for (int v = 0;v<G.V();v++){
            if (!marked[v]){
                dfs(G,v);
            }
        }
    }

    //基于深度优先搜索,把顶点排序
    private void dfs(Digraph G, int v){
        //标记当前v已经被搜索
        marked[v] = true;
        //通过循环深度搜索顶点v
        for (Integer w : G.adj(v)) {
            //如果当前顶点w没有搜索,则递归调用dfs进行搜索
            if (!marked[w]){
                dfs(G,w);
            }
        }
        //让顶点v进栈
        reversePost.push(v);
    }

    //获取顶点线性序列
    public Deque<Integer> reversePost(){
        return reversePost;
    }
}
public class Test {
    public static void main(String[] args) {
        Digraph g = new Digraph(6);
        g.addEdge(3,0);
        g.addEdge(1,0);
        g.addEdge(0,2);
        g.addEdge(2,1);
        g.addEdge(1,4);

        DepthFirstOrder cycle = new DepthFirstOrder(g);
        System.out.println(cycle.reversePost());
    }
}
[1, 0, 3, 2, 4, 5]

2 拓扑排序实现

  1. 基于一幅图,先检测有没有环(DirectedCycle);
  2. 如果没有环,再调用顶点排序(DepthFirstOrder),如此就实现了拓扑排序。
public class TopoLogical {
    //顶点的拓扑排序
    private Deque<Integer> order;

    //构造拓扑排序对象
    public TopoLogical(Digraph G) {
        //创建一个检测有向环的对象
        DirectedCycle cycle = new DirectedCycle(G);
        //判断G图中有没有环,如果没有环,则进行顶点排序:创建一个顶点排序对象
        if (!cycle.hasCycle()){
            DepthFirstOrder depthFirstOrder = new DepthFirstOrder(G);
            order = depthFirstOrder.reversePost();
        }
    }

    //判断图G是否有环
    public boolean isCycle(){
        return order==null;
    }

    //获取拓扑排序的所有顶点
    public Deque<Integer> order(){
        return order;
    }
}
public class Test {
    public static void main(String[] args) {
        Digraph g = new Digraph(6);
        g.addEdge(0,2);
        g.addEdge(0,3);
        g.addEdge(1,3);
        g.addEdge(2,4);
        g.addEdge(3,4);
        g.addEdge(4,5);

        TopoLogical cycle = new TopoLogical(g);
        System.out.println(cycle.order());
        System.out.println(cycle.isCycle());
    }
}
[1, 0, 3, 2, 4, 5]
false
  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

hellosc01

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

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

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

打赏作者

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

抵扣说明:

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

余额充值