拓扑排序

定义

给定一个包含n个节点的有向图,给出它的节点编号的一个排列,如果满足对于图中的任意一条有向边(u,v),u的排列都出现在v的前面,则称该排列是图的拓扑排序

  • 当图中存在环,那么它不存在拓扑排序
  • 当图是有向无环图,那么它的拓扑排序不止一种

对于拓扑排序有dfs和bfs两种方法可以求解

例题-课程顺序

 dfs解法

class Solution {
    // 存储有向图
    List<List<Integer>> edges;
    // 标记每个节点的状态:0=未搜索,1=搜索中,2=已完成
    int[] visited;
    // 结果数组
    int[] result;
    // 判断有向图中是否有环
    boolean valid = true;
    // 数组下标,由于dfs是倒着加入结果集的,所以需要从后往前添加
    int index;

    public int[] findOrder(int numCourses, int[][] prerequisites) {
        //初始化
        edges = new ArrayList<List<Integer>>();
        for (int i = 0; i < numCourses; ++i) {
            edges.add(new ArrayList<Integer>());
        }
        visited = new int[numCourses];
        result = new int[numCourses];
        index = numCourses - 1;
        //构造图
        for (int[] info : prerequisites) {
            edges.get(info[1]).add(info[0]);
        }
        // 每次挑选一个「未搜索」的节点,开始进行深度优先搜索
        for (int i = 0; i < numCourses && valid; ++i) {
            if (visited[i] == 0) {
                dfs(i);
            }
        }
        if (!valid) {
            return new int[0];
        }
        // 如果没有环,那么就有拓扑排序
        return result;
    }

    public void dfs(int u) {
        // 将节点标记为「搜索中」
        visited[u] = 1;
        // 搜索其相邻节点
        // 只要发现有环,立刻停止搜索
        for (int v: edges.get(u)) {
            // 如果「未搜索」那么搜索相邻节点
            if (visited[v] == 0) {
                dfs(v);
                if (!valid) {
                    return;
                }
            }
            // 如果「搜索中」说明找到了环
            else if (visited[v] == 1) {
                valid = false;
                return;
            }
        }
        // 将节点标记为「已完成」
        visited[u] = 2;
        // 将节点加入结果集
        result[index--] = u;
    }
}

BFS解法

   //记录下一个可以访问的数
    List<List<Integer>> canGo;
    //记录每个数的入度,当入度为0的时候可以直接访问
    int[] inDegNum;

    public int[] findOrder(int numCourses, int[][] prerequisites) {
        canGo = new ArrayList<>();
        for (int i = 0; i < numCourses; i++) {
            canGo.add(new ArrayList<>());
        }
        inDegNum = new int[numCourses];
        for (int[] prerequisite : prerequisites) {
            int to = prerequisite[0];
            int from = prerequisite[1];
            //to的入度增加,from可以造访的点添加to
            inDegNum[to]++;
            canGo.get(from).add(to);
        }
        Queue<Integer> queue = new LinkedList<>();
        //如果入度数为零则添加当前节点
        for (int i = 0; i < inDegNum.length; i++) {
            if (inDegNum[i] == 0) queue.add(i);
        }
        List<Integer> res = new ArrayList<>();
        //拓扑排序过程
        while (!queue.isEmpty()) {
            Integer cur = queue.poll();
            res.add(cur);
            for (Integer next : canGo.get(cur)) {
                inDegNum[next]--;
                if (inDegNum[next] == 0) queue.offer(next);
            }
        }//拓扑排序失败
        if (res.size() != numCourses) return new int[]{};
        return res.stream().mapToInt(Integer::intValue).toArray();
    }

bfs的算法步骤如下:

  • 构建有向图
  • 向队列中添加入度为0的节点(节点P),并且添加结果集
  • 遍历所有入度有P的节点,入度数量-1,如果为0,加入队列
  • 结果集数量不等于顶点数说明有环状结构导师结果集缺少,排除答案
  • 结果集数量等于顶点数说明找到了一个正确结果

进阶例题-外星文字典

注意这里判断排序未完成的那段代码

    //记录下一个可以访问的数
    List<List<Integer>> canGo;
    //记录每个数的入度,当入度为0的时候可以直接访问
    int[] inDeg;

    public String alienOrder(String[] words) {
        //初始化入度数组
        inDeg = new int[26];
        Arrays.fill(inDeg, -1);
        for (String word : words) {
            for (int i = 0; i < word.length(); i++) {
                inDeg[word.charAt(i) - 'a'] = 0;
            }
        }
        //初始化canGo
        canGo = new ArrayList<>(26);
        for (int i = 0; i < 26; i++) {
            canGo.add(new ArrayList<>());
        }
        //建图
        for (int i = 0; i < words.length; i++) {
            for (int j = i + 1; j < words.length; j++) {
                String w1 = words[i], w2 = words[j];
                int len = Math.min(w1.length(), w2.length());
                for (int k = 0; k < len; k++) {
                    if (w1.charAt(k) == w2.charAt(k)) {
                        //排除abc ab这种情况
                        if (k == len - 1 && w1.length() < w2.length()) {
                            return "";
                        }
                        continue;
                    }
                    int from = w1.charAt(k) - 'a';
                    int to = w2.charAt(k) - 'a';
                    canGo.get(from).add(to);
                    inDeg[to]++;
                    //每两个单词只贡献一次字符集顺序
                    break;
                }
            }
        }
        //添加入度为0的节点
        Queue<Character> queue = new LinkedList<>();
        for (int i = 0; i < 26; i++) {
            if (inDeg[i] == 0) queue.add((char) (i + 'a'));
        }
        //拓扑排序
        StringBuilder builder = new StringBuilder();
        while (!queue.isEmpty()) {
            Character cur = queue.poll();
            builder.append(cur);
            for (Integer next : canGo.get(cur - 'a')) {
                inDeg[next]--;
                if(inDeg[next]==0)queue.offer((char)(next+'a'));
            }
        }
        //检查所有入度节点是否都小于等于0,如果有一个还存在入度,说明排序未完成
        for (int i = 0; i < 26; ++i) if (inDeg[i] > 0) return "";
        return builder.toString();
    }

进阶例题-重建序列

此题对于其他拓扑排序增加了一个唯一的条件

    public boolean sequenceReconstruction(int[] org, List<List<Integer>> seqs) {
        int n = org.length;
        //用于判断特殊情况的set
        HashSet<Integer> set = new HashSet<>();
        for (List<Integer> seq : seqs) {
            set.addAll(seq);
        }
        //结果集不完整
        if (set.size() != n) return false;
        //只有一个元素,且不在org数组中
        if (n == 1 && !set.contains(org[0])) return false;
        //初始化,为了一一对应,多添加一个位置
        List<List<Integer>> canGo = new ArrayList<>(n + 1);
        int[] inDegree = new int[n + 1];
        for (int i = 0; i <= n; i++) {
            canGo.add(new ArrayList<>());
        }
        //建图过程
        for (List<Integer> seq : seqs) {
            for (int i = 0; i < seq.size() - 1; i++) {
                int from = seq.get(i), to = seq.get(i + 1);
                if (!canGo.get(from).contains(to)) {
                    canGo.get(from).add(to);
                    inDegree[to]++;
                }
            }
        }
        //拓扑排序
        Queue<Integer> queue = new LinkedList<>();
        for (int i = 1; i <= n; i++) {
            if (inDegree[i] == 0) {
                queue.offer(i);
            }
        }
        int[] res = new int[n];
        int index = 0;
        while (!queue.isEmpty()) {
            int size = queue.size();
            //唯一重建的体现
            if (size > 1) return false;
            Integer poll = queue.poll();
            res[index++] = poll;
            for (Integer integer : canGo.get(poll)) {
                inDegree[integer]--;
                if (inDegree[integer] == 0) queue.offer(integer);
            }
        }
        //判断是否为原数组
        return Arrays.equals(res, org);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值