优选算法-多源bfs解决拓扑排序问题

算法原理

什么是拓扑排序?

1.有向无环图(DAG图)

例如在下图中:2可以到4,但4不能到2

有方向,能从一个点到另一个点,但不能反

入度:有多少条边指向他

出度:指向有多少条边

2.AOV网:定带你活动图

在有向无环图中,用顶点来表示一个活动,用边来表示活动的先后顺序的图结构

3.拓扑排序

找到做事情的先后顺序,拓扑排序的结果可能并不唯一(拓扑排序的重要应用:判断有向图是否有环)

如何进行拓扑排序:

1.找出图中入度为0的点,然后输出

2.删除有该点连接的边

3.重复1,2操作,直到图中没有点或者没有入度为0的点为止(有可能有环)

4.实现拓扑排序

借助队列,一次bfs即可

1.初始化:把所有入度为0的点加入到队列中

2.当队列不为空的时候: 1.拿出队头元素,加入到最终结果中 2.删除与该元素相连的边 3.删除:与删除边相邻的点,是否入度为0,如果入度为0加入到队列中

题目解析

1.课程表

https://leetcode.cn/problems/course-schedule/description/

题目描述

你这个学期比逊选修numCourses们课程,记为0-numCourses-1

在选修某些课程之前需要先修一些课程,先修课程按照prerequisites给出,其中prerequisite[i]=[ai,bi]表示如果要修ai课程必须先修bi课程

请你判断是否能够完成所有课程的学习,如果可以,返回true 如果不行,返回false

算法原理

这就是一个典型的拓扑排序,这里问能否完成所有课程的学习,其实本质上就是问有向图中是否有环

我们利用拓扑排序来解决这一问题

那么如何建图呢?(这要求我们灵活使用容器)

1.看数据量

领接矩阵/领接表

代码如何写? 1.List<List<Integer>> edges 2.Map<Integer,List<Integer>> edges (Map是比较万能的)

2.根据算法流程,灵活建图

如何找到入度 int[] in = new int[]

代码实现

java 复制代码
class Solution {
    public boolean canFinish(int n, int[][] p) {
        Map<Integer,List<Integer>> map = new HashMap<>();
        int[] in = new int[n];
        for(int i = 0;i<p.length;i++){
            int a = p[i][0];
            int b = p[i][1];
            if(!map.containsKey(b)){
                map.put(b,new ArrayList<>());
            }
            map.get(b).add(a);
            in[a]++;
        }
        Queue<Integer> q = new LinkedList<>();
        for(int i = 0;i<n;i++){
            if(in[i]==0){
                q.add(i);
            }
        }
        while(!q.isEmpty()){
            int t = q.poll();
            for(int a:map.getOrDefault(t,new ArrayList<>())){
                in[a]--;
                if(in[a]==0){
                    q.add(a);
                }
            }
        }
        for(int x :in){
            if(x!=0){
                return false;
            }
        }
        return true;

    }
}

2.课程表二

https://leetcode.cn/problems/course-schedule-ii/description/

题目描述

现在你共有numCourses们课程,记为0到numCourses-1 给你一个数组p,其中p[i]=[ai,bi] 表示在选修ai之前必须选修bi

返回你为了学完所有课程安排的学习顺序,返回任意一种即可 如果无法完成,则返回一个空数组

算法原理

这道题和上一道课程表类似,我们都是使用拓扑排序来解决,只不过不同的是这里我们需要返回一个学习顺序

我们只需要在出队列的时候,将这个课程加入返回的数组即可

最后如果j==n 我们直接返回我们得到的数组

如果j<n 说明未能学习完,返回一个空数组即可

代码实现

java 复制代码
class Solution {
    public int[] findOrder(int n, int[][] p) {
        //1.准备工作
        List<List<Integer>> edges=new ArrayList<>();
        for(int i=0;i<n;i++){
            edges.add(new ArrayList<>());
        }
        int[] in=new int[n];
        //2.建图
        for(int i=0;i<p.length;i++){
            int a=p[i][0],b=p[i][1];
            edges.get(b).add(a);
            in[a]++;
        }
        //3.拓扑排序
        Queue<Integer> q=new LinkedList<>();
        for(int i=0;i<n;i++){
            if(in[i]==0){
                q.add(i);
            }
        }
        int[] ret=new int[n];
        int j=0;
        while(!q.isEmpty()){
            int t=q.poll();
            ret[j++]=t;
            for(int a:edges.get(t)){
                in[a]--;
                if(in[a]==0){
                    q.add(a);
                }
            }
        }
        if(j==n){
            return ret;
        }else{
            return new int[]{};
        }
    }
}
java 复制代码
class Solution {
    public int[] findOrder(int n, int[][] p) {
        Map<Integer,List<Integer>> map = new HashMap<>();
        int[] in = new int[n];
        for(int i=0;i<p.length;i++){
            int a = p[i][0];
            int b = p[i][1];
            if(!map.containsKey(b)){
                map.put(b,new ArrayList<>());
            }
            map.get(b).add(a);
            in[a]++;
        }
        Queue<Integer> q = new LinkedList<>();
        for(int i=0;i<n; i++){
            if(in[i]==0){
                q.add(i);
            }
        }
        int[] ret = new int[n];
        int j =0;
        while(!q.isEmpty()){
            int t = q.poll();
            ret[j++] = t;
            for(int a :map.getOrDefault(t,new ArrayList<>())){
                in[a]--;
                if(in[a]==0){
                    q.add(a);
                }
            }
        }
        if(j==n){
            return ret;
        }else{
            return new int[]{};
        }

    }
}

3.火星词典

https://leetcode.cn/problems/Jf1JuT/description/

题目描述

现有一种使用英语字母的外星文语言,这门语言的字母顺序与英语顺序不用,给定一个字符串列表words,words中的字符串已经按照新语言的字母顺序进行了排序,请你按照该词典还原出此语言已知的字母顺序,并且按照字母递增顺序排列,如果不存在合法的字母顺序,返回"",如果存在多种,返回其中的一种即可

字符串s字典顺序小于字符串t有两种情况:

1.在第一个不同字母处,如果s中的字母在这门外星语言的字母顺序中位于t字母之前,那么s的字典顺序小于t

2.如果前面的(s.length,t.length)字母都相同,那么s.length<t.length时,s的字典顺序也小于t

算法原理

1.如何搜集信息

两次for循环,用双指针

找一个入度为0的,进行拓扑排序

2.拓扑排序

1.建图(用哈希表hash<char,hash<char>> edges)

2.统计入度信息 hash<char,int>in 必须要初始化

3,如何搜集信息

双指针(只找到第一个不同的位置) 这里注意细节问题:abc ab 这种是不合法的

当我们遇到abc ab 这样的时候,直接返回空

代码实现

java 复制代码
class Solution {
    Map<Character,Set<Character>> edges = new HashMap<>();
    Map<Character,Integer> in = new HashMap<>();
    boolean check;
    public String alienOrder(String[] words) {
        //1.初始化建图
        for(String s: words){
            for(int i =0;i<s.length();i++){
                char ch = s.charAt(i);
                in.put(ch,0);
            }
        }
        int n =words.length;
        for(int i = 0;i<n;i++){
            for(int j =i+1;j<n;j++){
                add(words[i],words[j]);
                if(check==true){
                    return "";
                }
            }
        }
        //2.进行拓扑排序
        Queue<Character> q = new LinkedList<>();
        for(char ch:in.keySet()){
            if(in.get(ch)==0){
                q.add(ch);
            }
        }
        StringBuffer ret = new StringBuffer();
        while(!q.isEmpty()){
            char t = q.poll();
            ret.append(t);
            if(!edges.containsKey(t)){
                continue;
            }
            for(char ch:edges.get(t)){
                in.put(ch,in.get(ch)-1);
                if(in.get(ch)==0){
                    q.add(ch);
                }
            }
        }
        for(char ch : in.keySet()){
            if(in.get(ch)!=0){
                return "";
            }
        }
        return ret.toString();
    }

    public void add(String s1,String s2){
        int n = Math.min(s1.length(),s2.length());
        int i = 0;
        for(;i<n;i++){
            char c1=s1.charAt(i);
            char c2=s2.charAt(i);
            if(c1!=c2){
                //c1->c2;
                if(!edges.containsKey(c1)){
                    edges.put(c1,new HashSet<>());
                }
                if(!edges.get(c1).contains(c2)){
                    edges.get(c1).add(c2);
                    in.put(c2,in.get(c2)+1);
                }
                break;
            }
        }
        if(i==s2.length()&&i<s1.length()){
            check=true;
        }
    }
}
相关推荐
隔壁大炮2 小时前
10.PyTorch_元素类型转换
人工智能·pytorch·深度学习·算法
The Chosen One9852 小时前
算法题目分享(二分算法)
算法·职场和发展·蓝桥杯
玛丽莲茼蒿2 小时前
Leetcode hot100 矩阵置零【中等】
算法·leetcode·矩阵
澈2072 小时前
算法进阶:二叉树翻转与环形链表解析
数据结构·算法·排序算法
:1212 小时前
java数组2
java·算法·排序算法
代码飞天2 小时前
算法与数据结构之树——让数据查找更加迅速
数据结构·算法
贾斯汀玛尔斯2 小时前
每天学一个算法--外部排序(External Sorting)
算法
故事和你912 小时前
洛谷-算法2-2-常见优化技巧1
开发语言·数据结构·c++·算法·动态规划·图论
酉鬼女又兒2 小时前
JavaLeetCode 第一题「两数之和」:从暴力枚举到一遍哈希表的正确与错误实现,详解HashMap核心知识点及常见陷阱
java·开发语言·数据结构·算法·leetcode·职场和发展·散列表