基于有向图的排序方式 - 拓扑排序

一、拓扑排序的概念

在现实生活中,我们经常会同一时间接到很多任务去完成,但是这些任务的完成是有先后次序的。例如下图:

为了简化问题,我们使用整数为顶点编号的标准模型来表示这个案例:

 

这样当某个学生要学习这些课程的时候,需要给出一个学习的方案,我们只需要对图中的顶点进行排序,让它转换为一个线性序列,就可以解决问题。这时,我们就需要使用 拓扑排序 的算法。

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

 

 

 

二、检测有向图中的环

拓扑排序的前提是 必须保证图中没有环的存在,那么如何检测有向环呢?下面的代码是相应的解决方案。

package com.renhui.graph;

/**
 * 判断图中是否有环
 */
public class DirectedCycle {

    // 索引代表顶点,值表示顶点是否被搜索过
    private boolean[] marked;

    // 记录图中是否有环
    private boolean hasCycle;

    // 索引代表顶点,使用栈的思想,记录当前顶点没有已经处于正在搜索的有向路径上。
    private boolean[] onStack;

    private DirectedCycle(Digraph digraph) {
        marked = new boolean[digraph.V()];
        onStack = new boolean[digraph.V()];
        this.hasCycle = false;
        for (int v = 0; v < digraph.V(); v++) {
            if (!marked[v]) {
                dfs(digraph, v);
            }
        }
    }

    private void dfs(Digraph digraph, int v) {
        marked[v] = true;
        onStack[v] = true;
        for (Integer w : digraph.adj(v)) {
            if (!marked[w]) {
                dfs(digraph, w);
            }
            if (onStack[w]) {
                hasCycle = true;
                return;
            }
        }
        onStack[v] = false;
    }

    public boolean hasCycle() {
        return hasCycle;
    }

    public static void main(String[] args) {
        Digraph digraph = new Digraph(5);
        digraph.addEdge(3, 0);
        digraph.addEdge(0, 2);
        digraph.addEdge(2, 1);
        digraph.addEdge(1, 0);
        digraph.addEdge(1, 4);
        DirectedCycle directedCycle = new DirectedCycle(digraph);
        boolean isHasCycle = directedCycle.hasCycle();
        System.out.println("当前图是否存在环:" + isHasCycle);
    }

}

三、拓扑排序实现

首先我们需要知道拓扑排序的核心:顶点排序。通过一个栈来存储顶点,当我们深度搜索图的时候,每搜索完一个顶点,就把该顶点放入到reversePost中,这样就可以实现顶点排序。

顶点排序的实现:

package com.renhui.graph;

import java.util.Stack;

/**
 * 顶点排序
 */
public class DepthFirstOrder {

    private boolean[] marked;  // 索引代表顶点,值表示当前顶点是否已经被搜索

    private Stack<Integer> reversePost; // 使用栈,存储顶点序列

    // 创建一个顶点排序对象,生成顶点线性序列
    public DepthFirstOrder(Digraph digraph) {
        this.marked = new boolean[digraph.V()];
        this.reversePost = new Stack<>();

        // 遍历图中的每一个顶点,让每个顶点作为入口,完成一次深度优先搜索
        for (int v = 0; v < digraph.V() ; v++) {
            if (!marked[v]) {
                dfs(digraph, v);
            }
        }
    }

    // 基于深度优先搜索,生成顶点线性序列
    private void dfs(Digraph digraph, int v) {
        marked[v] = true;

        for (Integer w : digraph.adj(v)) {
            if (!marked[w]) {
                dfs(digraph, w);
            }
        }

        reversePost.push(v);
    }

    // 获取顶点线性序列
    public Stack<Integer> reversePost() {
        return reversePost;
    }

}

有了顶点排序和之前的环的检测,我们就可以很简单的实现拓扑排序了:

即:先检测有没有环,如果没有环,就调用顶点排序即可。

 

posted @ 2020-09-11 22:46  灰色飘零  阅读(159)  评论(0)    收藏  举报