算法 有向无环图 拓扑排序

32 篇文章 0 订阅

1. 如何构造图

  • 邻接矩阵(二维数组)
    图的邻接矩阵存储方式是用两个数组来表示图,一个一维数组存储图中顶点信息,一个二维数组存储图中边或弧的信息。

  • 邻接表
    图中顶点信息用一个一维数组存储,还需存储指向第一个邻接点的指针,以便于查找该顶点的边信息。
    其中每个顶点的所有邻接点构成一个线性表,由于邻接点的个数不定,所以用单链表存储。无向图成为顶点v的边表,有向图成为顶点v作为弧尾的出边表。

  • 图示
    图的表示

  • 等等其他表示方法

2. 拓扑排序概念

在图论中,如果一个有向图从任意顶点出发无法经过若干条边回到该点,则这个图是一个有向无环图(DAG图)。

由一个有向无环图的顶点组成的序列,当且仅当满足下列条件时,称为该图的一个拓扑排序(Topological sorting):

  1. 每个顶点出现且只出现一次;
  2. 若A在序列中排在B的前面,则在图中不存在从B到A的路径,即如果存在一条从顶点A到顶点B的路径(B依赖A),那么在排序结果中A在B的前面。

3. 拓扑排序代码实现

图的拓扑排序有两种算法:

/*
  构造图
*/
class Graph {

    // 图的表示:Map的key即所有顶点,value即与所关联的顶点
    private Map<Vertex, List<Vertex>> mGraph = new HashMap<>();


    // 添加一个节点
    void addNode(Vertex vertex) {
        if (mGraph.containsKey(vertex)) {
            return;
        }

        mGraph.put(vertex, null);
    }


    //添加一个有向边, node --> edge
    void addEdge(Vertex node, Vertex edge) {
        if (!mGraph.containsKey(node)) {
            //不包含该节点肯定不行
            return;
        }

        if (!mGraph.containsKey(edge)) {
            // 该图中都没有该节点,如何连边?先去添加node
            return;
        }


        List<Vertex> edges = mGraph.get(node);

        if (edges == null) {
            edges = new ArrayList<>();
            mGraph.put(node, edges);
        }

        edges.add(edge);
    }

    /**
     * 顶点个数
     */
    int nodesCount() {
        return mGraph.size();
    }

    /**
     * 获取节点的入度
     */
    int getIncomingEdges(Vertex node) {
        //获取所有其他节点指向它的个数
        Collection<List<Vertex>> values = mGraph.values();

        int incomingEdges = 0;

        for (List<Vertex> value : values) {
            if (value == null) continue;

            if (value.contains(node)) {
                ++incomingEdges;
            }
        }

        return incomingEdges;
    }

    List<Vertex> getIncomingNodes(Vertex node) {
        Collection<List<Vertex>> values = mGraph.values();

        List<Vertex> list = new ArrayList<>();

        for (List<Vertex> value : values) {
            if (value == null) continue;

            if (value.contains(node)) {
                list.add(node);
            }
        }

        return list;
    }


    /**
     * 获取节点的出度
     */
    int getOutgoingEdges(Vertex node) {
        if (!mGraph.containsKey(node)) {
            return 0;
        }

        return mGraph.get(node).size();
    }

    List<Vertex> getOutgoingNodes(Vertex node) {
        if (!mGraph.containsKey(node)) {
            return null;
        }

        return mGraph.get(node);
    }


    Set<Vertex> getNodes() {
        return mGraph.keySet();
    }

}
  1. Kahn算法
class KahnTopologicalSort {

    /*
        L ← Empty list that will contain the sorted elements
        S ← Set of all nodes with no incoming edges

        while S is non-empty do
            remove a node n from S
            add n to tail of L
            for each node m with an edge e from n to m do
                remove edge e from the graph
                if m has no other incoming edges then
                    insert m into S
        if graph has edges then
            return error (graph has at least one cycle)
        else
            return L (a topologically sorted order)
     */

    private Graph mGraph;
    private List<Vertex> mSortResult;// 拓扑排序后结果,即 L
    private Stack<Vertex> mSetOfZeroIndegree; // 入度为0的顶点队列,即 S

    private Map<Vertex, Integer> mIndegrees; //记录每个顶点当前的入度

    KahnTopologicalSort(Graph graph) {
        this.mGraph = graph;
        mSortResult = new ArrayList<>();
        mSetOfZeroIndegree = new Stack<>();
        mIndegrees = new HashMap<>();

        //初始化节点入读
        for (Vertex vertex : graph.getNodes()) {
            int incomingEdges = graph.getIncomingEdges(vertex);

            mIndegrees.put(vertex, incomingEdges);

            if (incomingEdges == 0) {
                mSetOfZeroIndegree.push(vertex);
            }
        }
    }

    List<Vertex> kahnSort() {

        while (!mSetOfZeroIndegree.isEmpty()) {
            Vertex poll = mSetOfZeroIndegree.pop();
            mSortResult.add(poll);

            List<Vertex> outgoingNodes = mGraph.getOutgoingNodes(poll);
            if (outgoingNodes == null) {
                continue;
            }

            for (Vertex vertex : outgoingNodes) {
                int degree = mIndegrees.get(vertex);
                --degree;
                mIndegrees.put(vertex, degree);

                if (0 == degree) {
                    mSetOfZeroIndegree.push(vertex);
                }
            }

        }

        if(mSortResult.size() != mGraph.nodesCount()){
            throw new RuntimeException("This graph contains cyclic dependencies");
        }

        return mSortResult;
    }
}
  1. 基于DFS的算法
class DFSTopologicalSort {
    /*

        L ← Empty list that will contain the sorted nodes
        while there are unmarked nodes do
            select an unmarked node n
            visit(n)

        function visit(node n)
            if n has a temporary mark then stop (not a DAG)
            if n is not marked (i.e. has not been visited yet) then
                mark n temporarily
                for each node m with an edge from n to m do
                    visit(m)
                mark n permanently
                unmark n temporarily
                add n to head of L

     */
    private Graph mGraph;
    private List<Vertex> mSortResult;
    private Set<Vertex> mSortTmpMarked;

    DFSTopologicalSort(Graph graph) {
        this.mGraph = graph;
        mSortResult = new ArrayList<>();
        mSortTmpMarked = new HashSet<>();
    }

    List<Vertex> DFSSort() {
        mSortResult.clear();
        mSortTmpMarked.clear();

        for (Vertex vertex : mGraph.getNodes()) {
            dfs(vertex, mSortResult, mSortTmpMarked);
        }

        Collections.reverse(mSortResult);
        return mSortResult;
    }

    private void dfs(Vertex node, List<Vertex> result, Set<Vertex> tmpMarked) {
        if (result.contains(node)) {
            return;
        }

        if (tmpMarked.contains(node)) {
            throw new RuntimeException("This graph contains cyclic dependencies");
        }

        tmpMarked.add(node);

        List<Vertex> outgoingNodes = mGraph.getOutgoingNodes(node);
        if (outgoingNodes != null) {
            for (Vertex outgoingNode : outgoingNodes) {
                dfs(outgoingNode, result, tmpMarked);
            }
        }

        tmpMarked.remove(node);
        result.add(node);
    }
}

4. 算法复杂度

O(E + V),结点个数和边个数。


参考:
图基本算法 图的表示方法 邻接矩阵 邻接表
拓扑排序的原理及其实现
Topological sorting

拓扑排序(Topological Sorting)
拓扑排序DFS的实现

  • 1
    点赞
  • 0
    评论
  • 6
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 精致技术 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值