有向无环图的拓扑排序

时间:2022-10-12 20:07:39

作者:Grey

原文地址:

博客园:有向无环图的拓扑排序

CSDN:有向无环图的拓扑排序

问题描述

给定一个有向图,图节点的拓扑排序定义如下:

  • 对于图中的每一条有向边 A -> B , 在拓扑排序中 A 一定在 B 之前.

  • 拓扑排序中的第一个节点可以是图中的任何一个没有其他节点指向它的节点.

针对给定的有向图找到任意一种拓扑排序的顺序.

题目链接:LintCode 127 · Topological Sorting

BFS 解法

BFS 解法就是最直接的解法就是判断入度为0的点,然后把入度为0的点加入队列进行宽度优先遍历。核心代码如下

        // 统计所有点的入度信息,放入 map 中
        Map<DirectedGraphNode, Integer> map = new HashMap<>();
        for (DirectedGraphNode node : graph) {
            map.putIfAbsent(node, 0);
            ArrayList<DirectedGraphNode> neighbors = node.neighbors;
            for (DirectedGraphNode n : neighbors) {
                if (!map.containsKey(n)) {
                    map.put(n, 1);
                } else {
                    map.put(n, map.get(n) + 1);
                }
            }
        }
        // 入度为0的点加入队列
        Queue<DirectedGraphNode> starts = new LinkedList<>();
        for (Map.Entry<DirectedGraphNode, Integer> entry : map.entrySet()) {
            if (entry.getValue() == 0) {
                starts.add(entry.getKey());
            }
        }
        // 使用队列进行宽度优先遍历
        ArrayList<DirectedGraphNode> ans = new ArrayList<>();
        while (!starts.isEmpty()) {
            DirectedGraphNode poll = starts.poll();
            ans.add(poll);
            // map.remove(poll);
            if (poll.neighbors != null && !poll.neighbors.isEmpty()) {
                for (DirectedGraphNode nb : poll.neighbors) {
                    if (map.get(nb) == 1) {
                        // map.remove(nb);
                        starts.offer(nb);
                    }
                    map.put(nb, map.get(nb) - 1);
                }
            }
        }

DFS 解法

本题也可以用深度优先遍历来解,深度优先遍历有两个思路

第一个思路:用点次来判断,如果:

X 这个点开始一直走到最后覆盖到的所有点的数量是 num1;

Y 这个点开始一直走到最后覆盖到的所有点的数量是 num2,如果 num1 > num2,

那么X 的拓扑序小于或者等于 Y。

不过要特别注意:每次遍历找某个点走到最后所有覆盖的次数不要重复计算(将之前已经算过的存入 Map 缓存即可),核心代码如下:

    // 当前遍历的节点是node,之前遍历的节点和出度关系存map中
    public static Info f(DirectedGraphNode node, Map<DirectedGraphNode, Info> map) {
        if (map.containsKey(node)) {
            return map.get(node);
        }
        long numOfNodes = 0;
        for (DirectedGraphNode neighbor : node.neighbors) {
            numOfNodes += f(neighbor, map).out;
        }
        Info info = new Info(node, numOfNodes + 1);
        map.put(node, info);
        return info;
    }

第二个思路:用某个点能延申的最大深度来判断,如果:

X 这个点开始一直走到最后的最大深度是 deep1;

Y 这个点开始一直走到最后的最大深度是 deep2,如果 deep1 > deep2,

那么X 的拓扑序小于或者等于 Y。 核心代码如下

    public static Record f2(DirectedGraphNode cur, HashMap<DirectedGraphNode, Record> order) {
        if (order.containsKey(cur)) {
            return order.get(cur);
        }
        int follow = 0;
        for (DirectedGraphNode next : cur.neighbors) {
            // 所有邻居的最大深度的最大值
            follow = Math.max(follow, f2(next, order).deep);
        }
        // 当前节点的最大深度是所有邻居节点的最大深度的最大值+1
        Record ans = new Record(cur, follow + 1);
        order.put(cur, ans);
        return ans;
    }

完整代码如下

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;


public class LintCode_0127_TopologicalSorting {

    public static class DirectedGraphNode {
        int label;
        ArrayList<DirectedGraphNode> neighbors;

        DirectedGraphNode(int x) {
            label = x;
            neighbors = new ArrayList<>();
        }
    }

    // DFS方式
    // 考察出度从大到小
    public static ArrayList<DirectedGraphNode> topSort(ArrayList<DirectedGraphNode> graph) {
        if (null == graph) {
            return new ArrayList<>();
        }
        Map<DirectedGraphNode, Info> map = new HashMap<>();
        for (DirectedGraphNode node : graph) {
            f(node, map);
        }
        List<Info> list = new ArrayList<>(map.values());
        list.sort((o1, o2) -> {
            if (o2.out > o1.out) {
                return 1;
            } else if (o2.out < o1.out) {
                return -1;
            }
            return 0;
        });
        ArrayList<DirectedGraphNode> ans = new ArrayList<>();
        for (Info info : list) {
            ans.add(info.node);
        }
        return ans;
    }

    // 当前遍历的节点是node,之前遍历的节点和出度关系存map中
    public static Info f(DirectedGraphNode node, Map<DirectedGraphNode, Info> map) {
        if (map.containsKey(node)) {
            return map.get(node);
        }
        long numOfNodes = 0;
        for (DirectedGraphNode neighbor : node.neighbors) {
            numOfNodes += f(neighbor, map).out;
        }
        Info info = new Info(node, numOfNodes + 1);
        map.put(node, info);
        return info;
    }

    public static class Info {
        public DirectedGraphNode node;
        public long out;

        public Info(DirectedGraphNode node, long out) {
            this.node = node;
            this.out = out;
        }
    }

    // DFS方式
    // 考察深度
    public static ArrayList<DirectedGraphNode> topSort2(ArrayList<DirectedGraphNode> graph) {
        HashMap<DirectedGraphNode, Record> order = new HashMap<>();
        for (DirectedGraphNode cur : graph) {
            f2(cur, order);
        }
        ArrayList<Record> recordArr = new ArrayList<>(order.values());
        recordArr.sort((o1, o2) -> (o2.deep - o1.deep));
        ArrayList<DirectedGraphNode> ans = new ArrayList<>();
        for (Record r : recordArr) {
            ans.add(r.node);
        }
        return ans;
    }

    public static Record f2(DirectedGraphNode cur, HashMap<DirectedGraphNode, Record> order) {
        if (order.containsKey(cur)) {
            return order.get(cur);
        }
        int follow = 0;
        for (DirectedGraphNode next : cur.neighbors) {
            // 所有邻居的最大深度的最大值
            follow = Math.max(follow, f2(next, order).deep);
        }
        // 当前节点的最大深度是所有邻居节点的最大深度的最大值+1
        Record ans = new Record(cur, follow + 1);
        order.put(cur, ans);
        return ans;
    }

    public static class Record {
        public DirectedGraphNode node;
        public int deep;

        public Record(DirectedGraphNode n, int o) {
            node = n;
            deep = o;
        }
    }

    // 使用BFS实现,
    // 入度为0
    // 已通过验证
    public ArrayList<DirectedGraphNode> topSort3(ArrayList<DirectedGraphNode> graph) {
        if (null == graph || graph.isEmpty()) {
            return new ArrayList<>();
        }
        // 遍历所有点,找到每个点的入度数据
        Map<DirectedGraphNode, Integer> map = new HashMap<>();
        for (DirectedGraphNode node : graph) {
            map.putIfAbsent(node, 0);
            ArrayList<DirectedGraphNode> neighbors = node.neighbors;
            for (DirectedGraphNode n : neighbors) {
                if (!map.containsKey(n)) {
                    map.put(n, 1);
                } else {
                    map.put(n, map.get(n) + 1);
                }
            }
        }
        // 入度为0的点加入队列
        Queue<DirectedGraphNode> starts = new LinkedList<>();
        for (Map.Entry<DirectedGraphNode, Integer> entry : map.entrySet()) {
            if (entry.getValue() == 0) {
                starts.add(entry.getKey());
            }
        }
        // 宽度优先遍历
        ArrayList<DirectedGraphNode> ans = new ArrayList<>();
        while (!starts.isEmpty()) {
            DirectedGraphNode poll = starts.poll();
            ans.add(poll);
            // map.remove(poll);
            if (poll.neighbors != null && !poll.neighbors.isEmpty()) {
                for (DirectedGraphNode nb : poll.neighbors) {
                    if (map.get(nb) == 1) {
                        // map.remove(nb);
                        starts.offer(nb);
                    }
                    map.put(nb, map.get(nb) - 1);
                }
            }
        }
        return ans;
    }
}

更多

算法和数据结构笔记

参考资料

算法和数据结构体系班-左程云