数据结构与算法之拓扑排序

1 定义和前置条件

1.1 定义

将有向图中的顶点以线性方式进行排序。即对于任何连接自顶点u到顶点v的有向边uv,在最后的排序结果中,顶点u总是在顶点v的前面。

假设我非常想学习一门机器学习的课程,但是在修这么课程之前,我们必须要学习一些基础课程,比如计算机科学概论,C语言程序设计,数据结构,算法等等。那么这个制定选修课程顺序的过程,实际上就是一个拓扑排序的过程,每门课程相当于有向图中的一个顶点,而连接顶点之间的有向边就是课程学习的先后关系。只不过这个过程不是那么复杂,从而很自然的在我们的大脑中完成了。将这个过程以算法的形式描述出来的结果,就是拓扑排序。

1.2 前置条件

一个有向图能被拓扑排序的充要条件就是它是一个有向无环图(DAG:Directed Acyclic Graph)。(即不存在环路)

2 分析

原图:
这里写图片描述

拓扑图图解:
这里写图片描述

拓扑排序结果:

顶点:v3
顶点:v1
顶点:v2
顶点:v6
顶点:v9
顶点:v10
顶点:v13
顶点:v0
顶点:v4
顶点:v5
顶点:v8
顶点:v7
顶点:v12
顶点:v11

3 典型实现算法

3.1 Kahn算法

3.1.1 伪代码
L←将包含已排序元素的空列表
S←没有传入边的所有节点的集合

while(S非空) {
     从S中删除节点n
     将n插入L
     对于具有从n到m的边e的每个节点m
         从图中删除边e
         如果m在下降沿没有其他边
               然后将m插入到S中
}
如果图形有边
      然后返回错误(图表至少有周期)
其他
     return L(一个拓扑有序序列)
2.1.2 代码
package com.dn.dijstra;

import java.util.ArrayList;
import java.util.Stack;

public class DnGraphTopologic {

    private int numVertexes;
    private VertexNode []  adjList;//邻接顶点的一维数组
    public DnGraphTopologic(int numVertexes){
        this.numVertexes = numVertexes;
    }

    private void createGraph(){
        VertexNode node0 = new VertexNode(0,"v0");//VertexNode(int in,String data)
        VertexNode node1 = new VertexNode(0,"v1");
        VertexNode node2 = new VertexNode(2,"v2");
        VertexNode node3 = new VertexNode(0,"v3");
        VertexNode node4 = new VertexNode(2,"v4");
        VertexNode node5 = new VertexNode(3,"v5");
        VertexNode node6 = new VertexNode(1,"v6");
        VertexNode node7 = new VertexNode(2,"v7");
        VertexNode node8 = new VertexNode(2,"v8");
        VertexNode node9 = new VertexNode(1,"v9");
        VertexNode node10 = new VertexNode(1,"v10");
        VertexNode node11 = new VertexNode(2,"v11");
        VertexNode node12 = new VertexNode(1,"v12");
        VertexNode node13 = new VertexNode(2,"v13");
        adjList = new VertexNode[numVertexes];

        adjList[0] =node0;
        adjList[1] =node1;
        adjList[2] =node2;
        adjList[3] =node3;
        adjList[4] =node4;
        adjList[5] =node5;
        adjList[6] =node6;
        adjList[7] =node7;
        adjList[8] =node8;
        adjList[9] =node9;
        adjList[10] =node10;
        adjList[11] =node11;
        adjList[12] =node12;
        adjList[13] =node13;

        node0.firstEdge = new EdgeNode(11);node0.firstEdge.next = new EdgeNode(5);node0.firstEdge.next.next = new EdgeNode(4);
        node1.firstEdge = new EdgeNode(8);node1.firstEdge.next = new EdgeNode(4);node1.firstEdge.next.next = new EdgeNode(2);
        node2.firstEdge = new EdgeNode(9);node2.firstEdge.next = new EdgeNode(6);node2.firstEdge.next.next = new EdgeNode(5);
        node3.firstEdge = new EdgeNode(13);node3.firstEdge.next = new EdgeNode(2);
        node4.firstEdge = new EdgeNode(7);
        node5.firstEdge = new EdgeNode(12);node5.firstEdge.next = new EdgeNode(8);
        node6.firstEdge = new EdgeNode(5);
        node8.firstEdge = new EdgeNode(7);
        node9.firstEdge = new EdgeNode(11);node9.firstEdge.next = new EdgeNode(10);
        node10.firstEdge = new EdgeNode(13);
        node12.firstEdge = new EdgeNode(9);
    }

    /**
     * 拓扑排序
     * 
     * 核心思想:每次从该栈中取出一个顶点,图形添加到拓扑排序的数量+1,将该顶点放入保存结果的集合中。
     * 紧接着循环遍历由该顶点引出的所有边,该顶点的入度减1;如果该顶点的入度在减去本条边之后为0,也将这个顶点放到入度为0的栈中。然后继续从栈中取出一个顶点…………
     * 当图形添加到拓扑排序的数量<图形的边,说明图中至少存在一条环路,抛出异常。相等的话则返回结果集合,此结果集合中的顺序就是对图进行拓扑排序的结果。
     * 
     */
    private ArrayList<String> topologicalSort() throws Exception{
        Stack<Integer> stack = new Stack<>();//存入入度为0边节点的栈
        ArrayList<String> array = new ArrayList<String>();//拓扑有序序列
        int count = 0;//图形添加到拓扑排序的数量
        int k = 0;
        for(int i = 0;i<numVertexes;i++ ){
            if(adjList[i].in == 0){//入度
                stack.push(i);//入度为0的顶点入栈
            }
        }
        while(!stack.isEmpty()){
            int pop = stack.pop();//从栈中取出一个顶点
            array.add(adjList[pop].data);//添加到拓扑有序序列中
            count++;
            //循环遍历由该顶点引出的所有边
            for(EdgeNode node = adjList[pop].firstEdge;node!=null;node = node.next){
                k = node.adjVert;//顶点下标
                if(--adjList[k].in == 0){//入度-1是否等于0
                    stack.push(k);//入度为0,入栈
                }
            }
        }
        if(count<numVertexes){//图形添加到拓扑排序的数量<图形的边,即是图形有边
            throw new Exception("完犊子了,拓扑排序失败");
        } 
        //否则返回一个拓扑有序序列
        return array;
    }

    /**
     * 边表顶点
     */
    class EdgeNode{
        private int adjVert;//顶点下标
        private EdgeNode next;//next指针
        private int weight;//权重

        public EdgeNode(int adjVert){
            this.adjVert = adjVert;
        }
        public int getAdjVert() {
            return adjVert;
        }
        public void setAdjVert(int adjVert) {
            this.adjVert = adjVert;
        }
        public EdgeNode getNext() {
            return next;
        }
        public void setNext(EdgeNode next) {
            this.next = next;
        }
        public int getWeight() {
            return weight;
        }
        public void setWeight(int weight) {
            this.weight = weight;
        }

    }

    /**
     * 邻接顶点
     */
    class VertexNode{
        private int in;//入度数量
        private String data;//顶点名称
        private EdgeNode firstEdge;//边表顶点

        public VertexNode(int in,String data){
            this.in = in;
            this.data = data;
        }

        public int getIn() {
            return in;
        }

        public void setIn(int in) {
            this.in = in;
        }

        public String getData() {
            return data;
        }

        public void setData(String data) {
            this.data = data;
        }

        public EdgeNode getFirstEdge() {
            return firstEdge;
        }

        public void setFirstEdge(EdgeNode firstEdge) {
            this.firstEdge = firstEdge;
        }

    }

    public static void main(String [] args){
        DnGraphTopologic dnGraphTopologic = new DnGraphTopologic(14);
        dnGraphTopologic.createGraph();
        try {
            ArrayList<String> array = dnGraphTopologic.topologicalSort();
            //遍历输入排序顶点
            for(int i = 0;i<array.size();i++ ){
                System.out.println("顶点:"+array.get(i));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3.2 基于DFS的拓扑排序

3.2.1 伪代码
L←将包含排序的节点的空列表
S←没有输出边的所有节点的集合

对于S中的每个节点n
     访问(n)
函数访问(节点n)
     如果n还没有被访问过
         标记为已访问
         对于具有从m到ndo的边的每个节点m
             访问(m)
         将n添加到L.
3.2.2 分析

DFS的实现更加简单直观,使用递归实现。利用DFS实现拓扑排序,实际上只需要添加一行代码,即上面伪码中的最后一行:add n to L。

4 链接

拓扑排序的原理及其实现

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值