《算法4》有向图 (二、有向无环图(DAG) | 拓扑排序 | 强连通分量(Kosaraju))

基于《算法4》的描述,在之前有向图数据结构基础上,实现有向无环图(DAG)、拓扑排序、强连通分量(Kosaraju)算法;

一些概念

有向无环图(DAG):不含有环的有向图;
拓扑排序:
    给定一幅有向图,使得有向边均从排在前面的元素指向排在后面的元素;
    当且仅当,有向无环图才可以做拓扑排序;
    常用于任务调度、课程安排...等解决方案;
强连通分量(Kosaraju)
    顶点v和顶点w互相可达为连通性;
    顶点之间可达的最大子集,就是强连通分量; 

一、有向无环图(DAG)

有向无环图检测算法类 DirectedCycle.java

/**
 * 有向无环图(DAG)
 *
 * @Author: ltx
 * @Description: DAG有向环检测
 *
 */
public class DirectedCycle {
    private DirectedGraph graph;//图
    public boolean marked[];//标记已经遍历过的顶点
    public Integer edgeTo[];//路径
    private Stack cycle;//形成环的节点
    private boolean[] onStack;//标记是否形成环

    /**
     * 初始化
     *
     * @param graph
     */
    public DirectedCycle(DirectedGraph graph) {
        this.graph = graph;
        marked = new boolean[graph.v];
        edgeTo = new Integer[graph.v];
        onStack = new boolean[graph.v];
        for (int i = 0; i < graph.v; i++) {
            if (!marked[i]) {
//                System.out.printf("起点 %d, 开始遍历...\n", i);
                //深度度优先遍历
                dfs(i);
//                System.out.println();
            }
        }
    }

    /**
     * 深度优先遍历
     *
     * @param v 起点
     */
    public void dfs(int v) {
//        System.out.printf("%d, ", v);
        onStack[v] = true;
        //标记已走
        marked[v] = true;
        for (int w : graph.adj[v]) {
            if (!marked[w]) {
                //路径
                edgeTo[w] = v;
                dfs(w);
            } else if (onStack[w]) {
                cycle = new Stack();
                for (int x = v; x != v; x = edgeTo[x]) {
                    cycle.push(x);
                }
                cycle.push(w);
                cycle.push(v);
            }
        }
        onStack[v] = false;
    }

    /**
     * 有环
     *
     * @return
     */
    public boolean hasCycle() {
        return cycle != null;
    }

    /**
     * 初始化有向图
     *
     * @return
     */
    public static DirectedGraph init() {
        DirectedGraph graph = new DirectedGraph(13);
        graph.addEdge(0, 5);
        graph.addEdge(0, 1);
        graph.addEdge(0, 6);
        graph.addEdge(2, 0);
        graph.addEdge(2, 3);
        graph.addEdge(3, 5);
        graph.addEdge(5, 4);
        graph.addEdge(6, 4);
        graph.addEdge(6, 9);
        graph.addEdge(7, 6);
        graph.addEdge(8, 7);
        graph.addEdge(9, 11);
        graph.addEdge(9, 12);
        graph.addEdge(9, 10);
        graph.addEdge(11, 12);
        return graph;
    }

    public static void main(String[] args) {
        System.out.println("################有向图################");
        //初始化一个图(来自《算法4》的有向图demo)
        DirectedGraph graph = DirectedCycle.init();
        System.out.println("-------------------邻接表结构-------------------");
        graph.show();
        DirectedCycle directedCycle = new DirectedCycle(graph);
        System.out.printf("是否有环: %s\n", directedCycle.hasCycle());
    }
}

二、拓扑排序

拓扑排序算法实现的测试用例图:

《算法4》有向图 (二、有向无环图(DAG) | 拓扑排序 | 强连通分量(Kosaraju))_第1张图片

有向无环图拓扑排序算法类 DepthFirstOrder.java

/**
 * 拓扑排序
 * @Author: ltx
 * @Description: 有向无环图拓扑排序
 */
public class DepthFirstOrder {
    private DirectedGraph graph;//图
    public boolean marked[];//标记已经遍历过的顶点
    Queue pre;     //所有顶点前序排列(队列)
    Queue post;    //所有顶点后序排列(队列)
    Stack reversePost; //所有顶点逆后序排列(栈)--拓扑排序

    /**
     * 初始化
     *
     * @param graph
     */
    public DepthFirstOrder(DirectedGraph graph) {
        this.graph = graph;
        pre = new LinkedList<>();
        post = new LinkedList<>();
        reversePost = new Stack<>();
        marked = new boolean[graph.v];
        for (int i = 0; i < graph.v; i++) {
            if (!marked[i]) {
                //深度度优先遍历
                dfs(i);
            }
        }
    }

    /**
     * 深度优先遍历
     *
     * @param v 起点
     */
    public void dfs(int v) {
        //前序
        pre.add(v);
        //标记已走
        marked[v] = true;
        for (int w : graph.adj[v]) {
            if (!marked[w]) {
                dfs(w);
            }
        }
        //后续
        post.add(v);
        //逆后续
        reversePost.push(v);
    }

    /**
     * 初始化有向图
     *
     * @return
     */
    public static DirectedGraph init() {
        DirectedGraph graph = new DirectedGraph(13);
        graph.addEdge(0, 5);
        graph.addEdge(0, 1);
        graph.addEdge(0, 6);
        graph.addEdge(2, 0);
        graph.addEdge(2, 3);
        graph.addEdge(3, 5);
        graph.addEdge(5, 4);
        graph.addEdge(6, 4);
        graph.addEdge(6, 9);
        graph.addEdge(7, 6);
        graph.addEdge(8, 7);
        graph.addEdge(9, 11);
        graph.addEdge(9, 12);
        graph.addEdge(9, 10);
        graph.addEdge(11, 12);
        return graph;
    }

    public static void main(String[] args) {
        System.out.println("################有向图################");
        //初始化一个图(来自《算法4》的有向图demo)
        DirectedGraph graph = DepthFirstOrder.init();
        System.out.println("-------------------邻接表结构-------------------");
        graph.show();
        System.out.println("-------------------是否有环-------------------");
        DirectedCycle directedCycle = new DirectedCycle(graph);
        System.out.printf("是否有环: %s\n", directedCycle.hasCycle());
        System.out.println("-------------------拓扑排序-------------------");
        if (!directedCycle.hasCycle()) {
            DepthFirstOrder depthFirstOrder = new DepthFirstOrder(graph);
            System.out.println("前序:");
            System.out.println(depthFirstOrder.pre);
            System.out.println("后序:");
            System.out.println(depthFirstOrder.post);
            System.out.println("逆后序(拓扑排序):");
            List temp = new ArrayList<>();
            while (!depthFirstOrder.reversePost.isEmpty()) {
                temp.add(depthFirstOrder.reversePost.pop());
            }
            System.out.println(temp);
        }
    }
}

《算法4》有向图 (二、有向无环图(DAG) | 拓扑排序 | 强连通分量(Kosaraju))_第2张图片

三、强连通分量(Kosaraju)

强连通分量(Kosaraju)算法实现的测试用例图:

《算法4》有向图 (二、有向无环图(DAG) | 拓扑排序 | 强连通分量(Kosaraju))_第3张图片

算法图解:

《算法4》有向图 (二、有向无环图(DAG) | 拓扑排序 | 强连通分量(Kosaraju))_第4张图片

有向图强连通分量算法类 Kosaraju.java

/**
 * @Author: ltx
 * @Description: 有向图强连通分量算法
 */
public class Kosaraju {
    private DirectedGraph graph;//图
    public boolean marked[];//标记已经遍历过的顶点
    public int[] id;//连通分量顶点
    public int count;//连通分量数量

    /**
     * 初始化
     *
     * @param graph
     */
    public Kosaraju(DirectedGraph graph) {
        this.graph = graph;
        marked = new boolean[graph.v];
        id = new int[graph.v];
        // 反向图
        System.out.println("-------------------邻接表结构(反向图)-------------------");
        graph.reverse().show();
        // 逆后序(拓扑排序)
        DepthFirstOrder depthFirstOrder = new DepthFirstOrder(graph.reverse());
        System.out.println("反向图的逆后序:");
        List temp = new ArrayList<>();
        while (!depthFirstOrder.reversePost.isEmpty()) {
            temp.add(depthFirstOrder.reversePost.pop());
        }
        System.out.println(temp);
        //按方向图的拓扑排序做深度优先遍历
        for (int i : temp) {
            if (!marked[i]) {
                //深度度优先遍历
                dfs(i);
                count++;
            }
        }
    }

    /**
     * 深度优先遍历
     *
     * @param v 起点
     */
    public void dfs(int v) {
        //标记已走
        marked[v] = true;
        //记录强连通分量数量
        id[v] = count;
        for (int w : graph.adj[v]) {
            if (!marked[w]) {
                dfs(w);
            }
        }
    }

    /**
     * 初始化有向图
     *
     * @return
     */
    public static DirectedGraph init() {
        DirectedGraph graph = new DirectedGraph(13);
        graph.addEdge(0, 1);
        graph.addEdge(0, 5);
        graph.addEdge(2, 0);
        graph.addEdge(2, 3);
        graph.addEdge(3, 2);
        graph.addEdge(3, 5);
        graph.addEdge(4, 2);
        graph.addEdge(4, 3);
        graph.addEdge(5, 4);
        graph.addEdge(6, 0);
        graph.addEdge(6, 4);
        graph.addEdge(6, 9);
        graph.addEdge(7, 6);
        graph.addEdge(7, 8);
        graph.addEdge(8, 7);
        graph.addEdge(8, 9);
        graph.addEdge(9, 10);
        graph.addEdge(9, 11);
        graph.addEdge(10, 12);
        graph.addEdge(11, 4);
        graph.addEdge(11, 12);
        graph.addEdge(12, 9);
        return graph;
    }

    /**
     * 打印强连通分量
     */
    public void printWeight() {
        for (int temp = 0; temp < count; temp++) {
            System.out.printf("第 %d 个: ", temp + 1);
            for (int i = 0; i < id.length; i++) {
                if (this.id[i] == temp) {
                    System.out.printf("%d, ", i);
                }
            }
            System.out.println();
        }
    }

    public static void main(String[] args) {
        System.out.println("################有向图################");
        //初始化一个图(来自《算法4》的有向图demo)
        DirectedGraph graph = Kosaraju.init();
        System.out.println("-------------------邻接表结构-------------------");
        graph.show();
        Kosaraju kosaraju = new Kosaraju(graph);
        System.out.println("-------------------强连通分量-------------------");
        System.out.printf("强连通分量 %d 个:\n", kosaraju.count);
        kosaraju.printWeight();
    }
}

《算法4》有向图 (二、有向无环图(DAG) | 拓扑排序 | 强连通分量(Kosaraju))_第5张图片

你可能感兴趣的:(数据结构和算法,数据结构,算法)