拓扑排序AOV网

前言

若用DAG图表示一个工程,其顶点表示活动,用有向边<Vi,Vj>表示活动Vi必须先于活动Vj进行的这样一种关系,则将这种有向图称为顶点表示活动的网络,即AOV网。在AOV网中,可以寻找是否有一条路径,让每个活动有序进行,即是否有拓扑排序。

一、案例

在这里插入图片描述

二、题解

package com.xhu.offer.offerII;

import java.util.*;

//课程顺序
public class FindOrder {
    //DS-Set + 理清思路
    //bug1:第35行,找到了符合的节点之后,没有加break,一直寻找。
    public int[] findOrder(int numCourses, int[][] prerequisites) {
        int[] order = new int[numCourses];

        int[] visited = new int[numCourses];
        int count = 0;

        List<Set<Integer>> edges = new ArrayList<>();
        for (int i = 0; i < numCourses; i++) edges.add(new HashSet<>());

        for (int[] prerequisite : prerequisites) {
            Set<Integer> edge = edges.get(prerequisite[0]);

            edge.add(prerequisite[1]);
        }
        for (int i = 0; i < numCourses; i++) {
            boolean flag = false;
            for (int j = 0; j < edges.size(); j++) {
                if (visited[j] == 0 && edges.get(j).size() == 0) {
                    order[count++] = j;
                    visited[j] = 1;

                    for (Set<Integer> edge : edges) {
                        edge.remove(j);
                    }

                    flag = true;
                    break;
                }
            }
            if (!flag) return new int[]{};
        }

        return order;
    }

    //DS-Stack
    public int[] findOrder2(int numCourses, int[][] prerequisites) {
        int[] order = new int[numCourses];

        int[] visited = new int[numCourses];
        int count = 0;

        Stack<Integer> stack = new Stack<>();

        List<Set<Integer>> edges = new ArrayList<>();
        for (int i = 0; i < numCourses; i++) edges.add(new HashSet<>());

        for (int[] prerequisite : prerequisites) {
            Set<Integer> edge = edges.get(prerequisite[0]);

            edge.add(prerequisite[1]);
        }

        for (int i = 0; i < edges.size(); i++) {
            if (visited[i] == 0 && edges.get(i).size() == 0) {
                visited[i] = 1;
                stack.push(i);
            }
        }
        while (!stack.isEmpty()) {
            int i = stack.pop();
            order[count++] = i;

            for (int j = 0; j < edges.size(); j++) {
                if (visited[j] == 1) continue;

                edges.get(j).remove(i);
                if (0 == edges.get(j).size()) {
                    stack.push(j);
                    visited[j] = 1;
                }
            }
        }

        return count == numCourses ? order : new int[]{};
    }


    public static void main(String[] args) {
        int n = 2;
        int[][] data = new int[][]{{1, 0}};
        new FindOrder().findOrder(n, data);
    }

}

总结

1)拓扑排序
2)没有小错误就Debug,因为思想固化之后,逻辑会看不清楚,全部变为我以为的,调试可以看到真实的逻辑所展示的执行过程。

参考文献

[1]LeetCode 课程顺序

附录

1、外星文字典

在这里插入图片描述

package com.xhu.offer.offerII;

import java.util.*;

//外星人字典
public class AlienOrder {
    //core:26个小写字母,用单词顺序构建字典树,树的孩子节点添加按位置递增来加入,即表示它们之间的大小。
    //再加入的过程中看当前字符是否已经在之间加入过,这个可以用HashMap来确定以前加入的位置,如果为length,则不管,否则返回空,表示不合法。
    //在合法的构建字典树之后,每一个节点的孩子节点都可构成一个有向图,然后DFS来寻找是否有一条可达路径包含了所有字符
    //总结:DS-plugin-Trie + DS-plugin-HashMap + DS-plugin-List + component-树遍历 + Pre-Graph + component-DFS
    public String alienOrder(String[] words) {
        final String none = "";
        //构建字典树
        boolean isTrie = trie(words);
        if (!isTrie) return none;
        //Pre-Graph
        Queue<TreeNode> queue = new LinkedList<>();

        queue.offer(root);
        while (!queue.isEmpty()) {
            TreeNode cur = queue.poll();

            if (cur.isEnd) continue;

            addEdge(cur, queue);
        }


        //拓扑排序-Stack
        StringBuilder sb = new StringBuilder();
        Stack<Integer> stack = new Stack<>();
        int[] visited = new int[26];

        for (int i = 0; i < edges.size(); i++) {
            if (edges.get(i).size() == 0 && visited[i] == 0) {
                stack.push(i);
                visited[i] = 1;
            }
        }
        if (stack.isEmpty()) return none;
        while (!stack.isEmpty()) {
            int idx = stack.pop();
            sb.append(chs[idx]);
            //remove
            for (int i = 0; i < edges.size(); i++) {
                edges.get(i).remove(idx);
                if (visited[i] == 0 && edges.get(i).size() == 0) {
                    stack.push(i);
                    visited[i] = 1;
                }
            }
        }
        return sb.length() == nodeNum ? sb.toString() : none;
    }


    private void addEdge(TreeNode cur, Queue<TreeNode> queue) {
        TreeNode[] next = cur.next;
        queue.offer(next[0]);

        for (int i = 0; i < next.length - 1; i++) {
            if (next[i] != null) addNode(next[i].val);
            if (next[i + 1] != null) {
                addNode(next[i + 1].val);

                queue.offer(next[i + 1]);

                int idx = nodes.get(next[i + 1].val);
                Set<Integer> edge = edges.get(idx);
                edge.add(nodes.get(next[i].val));
            }
        }
    }

    private void addNode(char val) {
        if (!nodes.containsKey(val)) {
            nodes.put(val, nodeNum++);
            chs[nodeNum - 1] = val;
            edges.add(new HashSet<>());
        }
    }

    Map<Character, Integer> nodes = new HashMap<>();//将节点映射成idx,配合edges。
    char[] chs = new char[26];//通过id反向得到val
    List<Set<Integer>> edges = new ArrayList<>();//用数组式的邻接表存储边。
    int nodeNum = 0;//记录有多少节点,顺便作为每个节点的index。

    private boolean trie(String[] words) {
        for (String word : words) {
            char[] chs = word.toCharArray();
            TreeNode cur = root;
            for (char ch : chs) {

                int idx = cur.index.getOrDefault(ch, -1);

                if (idx == -1) idx = cur.count++;
                if (idx < cur.count - 1) return false;
                if (cur.next[idx] == null) {
                    cur.next[idx] = new TreeNode();
                    cur.next[idx].val = ch;
                }
                if (!cur.index.containsKey(ch)) cur.index.put(ch, idx);

                if (cur.isEnd) cur.isEnd = false;

                cur = cur.next[idx];
            }
            if (cur.next[0] == null) cur.isEnd = true;//用例有问题,该处改为cur.isEnd = true; + 用set计有多少字符 + return nums.size() == sb.length()
        }
        return true;
    }

    TreeNode root = new TreeNode();

    {
        root.val = '#';
    }

    public class TreeNode {
        char val;
        int count;
        boolean isEnd;

        TreeNode[] next = new TreeNode[26];

        Map<Character, Integer> index = new HashMap<>();
    }
}

2、重建序列

在这里插入图片描述

package com.xhu.offer.offerII;

import java.util.*;

//重建序列(题目要求重建的序列必须是唯一序列,但是案例不是唯一的)
public class SequenceReconstruction {
    //Pre-Graph-Map + DS-Queue + component-(逆)拓扑排序
    public boolean sequenceReconstruction(int[] org, List<List<Integer>> seqs) {
        //构建有向图,设置好每个节点的入度。
        for (List<Integer> seq : seqs) {

            //构建该节点值为id 的 所有边。
            int size = seq.size();
            addNode(seq.get(0));
            for (int i = 0; i < size - 1; i++) {
                addNode(seq.get(i));
                addNode(seq.get(i + 1));
                int id = seq.get(i), nextId = seq.get(i + 1);
                Set<Integer> edgeForI = graph.get(id);
                if (!edgeForI.contains(nextId)) {
                    //更新入度
                    in.put(nextId, in.get(nextId) + 1);
                    edgeForI.add(nextId);
                }


            }
        }
        int len = org.length;
        if (len != graph.size()) return false;
        //拓扑排序
        //1-找到入度为0的节点,让它进入队列
        Queue<Integer> sort = new LinkedList<>();
        for (int id : in.keySet()) {
            int inDegree = in.get(id);
            if (inDegree == 0) sort.offer(id);
        }
        if (sort.size() != 1) return false;

        //开始拓扑排序
        int count = 0;
        while (!sort.isEmpty()) {
            if (sort.size() != 1) return false;
            int id = sort.poll();


            if (id != org[count++]) return false;
            //更新入度
            Set<Integer> edgeForId = graph.get(id);
            for (Integer nextId : edgeForId) {
                in.put(nextId, in.get(nextId) - 1);
                if (in.get(nextId) == 0) sort.offer(nextId);

            }
        }
        return count == len ? true : false;
    }

    private void addNode(Integer id) {
        if (!graph.containsKey(id)) {
            graph.put(id, new HashSet<>());
            //设置入度
            in.put(id, 0);
        }
    }

    Map<Integer, Set<Integer>> graph = new HashMap<>();//有向图构造
    Map<Integer, Integer> in = new HashMap<>();//每个节点的入读。
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值