无回路有向图(Directed Acyclic Graph)的拓扑排序

无回路有向图(Directed Acyclic Graph)的拓扑排序:

        package com.neusoft.data.structure;

/**
 * Java: 无回路有向图(Directed Acyclic Graph)的拓扑排序
 *       该DAG图是通过邻接表实现的。
 * @date 2015/12/20
 */
import java.util.List;
import java.util.ArrayList;
import java.util.Queue;
import java.util.LinkedList;
public class TopListSort {
    // 邻接表中表对应的链表的顶点
    private class ENode {
        int itop;       // 该边所指向的顶点的位置
        ENode next; // 指向下一条弧的指针
    }

    // 邻接表中表的顶点
    private class VNode {
        char data;          // 顶点信息
        ENode firstEdge;    // 指向第一条依附该顶点的弧
    };

    private List<VNode> mVexs;  // 顶点数组
    /*
     * 创建图(用已提供的矩阵)
     *
     * 参数说明:
     *     vexs  -- 顶点数组
     *     edges -- 边数组
     */
    public TopListSort(char[] vexs, char[][] edges) {//做成邻接表

        // 初始化"顶点数"和"边数"
        int vlen = vexs.length;//顶点数
        int elen = edges.length;//边数

        // 初始化"顶点"
        mVexs = new ArrayList<VNode>();
        for (int i = 0; i < vlen; i++) {
            // 新建VNode
            VNode vnode = new VNode();// 邻接表中表的顶点
            vnode.data = vexs[i];
            vnode.firstEdge = null;//初始化顶点时,都没有指向该顶点的弧
            // 将vnode添加到数组mVexs中
            mVexs.add(vnode);
        }
        // 初始化"边"
        for (int i = 0; i < elen; i++) {//elem:边数
            // 读取边的起始顶点和结束顶点
            char c1 = edges[i][0];
            char c2 = edges[i][1];
            // 读取边的起始顶点和结束顶点
            int p1 = getPosition(c1);//返回起始顶点在顶点数组中的位置
            int p2 = getPosition(c2);//返回结束顶点在顶点数组中的位置

            // 初始化node1
            ENode node1 = new ENode();
            node1.itop = p2;//itop:顶点位置
            // 将node1链接到"p1所在链表的末尾"
            if(mVexs.get(p1).firstEdge == null){
                mVexs.get(p1).firstEdge = node1;
            }else{//将node节点链接到list的最后
                linkLast(mVexs.get(p1).firstEdge, node1);
            }
        }
    }

    /*
     * 将node节点链接到list的最后
     */
    private void linkLast(ENode list, ENode node) {
        ENode p = list;

        while(p.next!=null){
            p = p.next;
        }
        p.next = node;
    }

    /*
     * 返回ch在顶点数组中的位置
     */
    private int getPosition(char ch) {
        for(int i=0; i<mVexs.size(); i++){
            if(mVexs.get(i).data==ch){
                return i;
            }
        }
        return -1;
    }
    /*
     * 打印矩阵队列图
     */
    public void print() {
        System.out.printf("== List Graph:\n");
        for (int i = 0; i < mVexs.size(); i++) {
            System.out.printf("%d(%c): ", i, mVexs.get(i).data);
            ENode node = mVexs.get(i).firstEdge;
            while (node != null) {
                System.out.printf("%d(%c) ", node.itop, mVexs.get(node.itop).data);
                node = node.next;
            }
            System.out.printf("\n");
        }
    }

    /*
     * 拓扑排序
     *
     * 返回值:
     *     -1 -- 失败(由于内存不足等原因导致)
     *      0 -- 成功排序,并输入结果
     *      1 -- 失败(该有向图是有环的)
     */
    public int topologicalSort() {
        int index = 0;
        int num = mVexs.size();//顶点数组的个数

        int[] in   = new int[num];//// 入度数组
        char[] results  = new char[num];// 拓扑排序结果数组,记录每个节点的排序后的序号。
        Queue<Integer> queue = new LinkedList<Integer>();// 辅助队列

        // 统计每个顶点的入度数
        for(int i = 0; i < num; i++) {
            ENode node = mVexs.get(i).firstEdge;
            while (node != null) {
                in[node.itop]++;
                node = node.next;
            }
        }

        // 将所有入度为0的顶点入队列,
        for(int i = 0; i < num; i ++){
            if(in[i] == 0){
                queue.offer(i);                 // 入队列
            }
        }

        while (!queue.isEmpty()) {              // 队列非空
            int j = queue.poll().intValue();    // 出队列。j是顶点的序号
            results[index++] = mVexs.get(j).data;  // 将该顶点添加到results中,results是排序结果
            ENode node = mVexs.get(j).firstEdge;// 获取以该顶点为起点的出边队列

            // 将与"node"关联的节点的入度减1;
            // 若减1之后,该节点的入度为0;则将该节点添加到队列中。
            while(node != null) {
                // 将节点(序号为node.ivex)的入度减1。
                in[node.itop]--;
                // 若节点的入度为0,则将其"入队列"
                if(in[node.itop] == 0){
                    queue.offer(node.itop);    // 入队列
                }

                node = node.next;
            }
        }

        if(index != num) {
            System.out.printf("图是个环!\n");
            return 1;
        }

        // 打印拓扑排序结果
        System.out.printf("== TopSort: ");
        for(int i = 0; i < num; i ++){
            System.out.printf("%c ", results[i]);
        }
        System.out.printf("\n");

        return 0;
    }

    public static void main(String[] args) {
        char[] vexs = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};//顶点数组
        char[][] edges = new char[][]{//边数组,例如A->G B->A等
                {'A', 'G'},
                {'B', 'A'},
                {'B', 'D'},
                {'C', 'F'},
                {'C', 'G'},
                {'D', 'E'},
                {'D', 'F'}};


        // 自定义"图"(输入矩阵队列)
        //pG = new ListDG();
        // 采用已有的"图"
        TopListSort pG = new TopListSort(vexs, edges);

        pG.print();   // 打印图

        pG.topologicalSort();     // 拓扑排序
    }
}


      

输出:

        == List Graph:
0(A): 6(G) 
1(B): 0(A) 3(D) 
2(C): 5(F) 6(G) 
3(D): 4(E) 5(F) 
4(E): 
5(F): 
6(G): 
== TopSort: B C A D G E F 
      

分析:

        拓扑排序
它是对有向图的顶点排成一个线性序列。
这个序列也不唯一
比如学习java系类(部分)从java基础,到jsp/servlet,到ssm,到springboot,springcloud等是个循序渐
进且有依赖的过程。


正常步骤为(方法不一定唯一):
	从DGA图中找到一个没有前驱的顶点输出。(可以遍历,也可以用优先队列维护)
	删除以这个点为起点的边。(它的指向的边删除,为了找到下个没有前驱的顶点)
	重复上述,直到最后一个顶点被输出。如果还有顶点未被输出,则说明有环!
	
	
	
这样就完成一次拓扑排序,得到一个拓扑序列,但是这个序列并不唯一!从过程中也看到有很多选择方案,
具体
得到结果看你算法的设计了。但只要满足即是拓扑排序序列。
      

图解步骤:

v2-6b370e2d4ad53e7a218c1d87b1e96847_b.jpg

v2-31fbec59fab8bee6aa25a7d3cc942025_b.jpg

v2-ef5cc2edd125cc53a06107641a6f985a_b.jpg

v2-b67c7c6a349f779fcfa8820a66ddfdf9_b.jpg

v2-d7c8709995db6aa87315a838222473e0_b.jpg

发布了158 篇原创文章 · 获赞 18 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/yunfengfengfeng/article/details/105555006