【LeetCode题目拓展】第207题 课程表 拓展(拓扑排序、Tarjan算法、Kosaraju算法)

news2024/12/27 4:17:49

文章目录

    • 一、拓扑排序题目
    • 二、题目拓展
      • 1. 思路分析
      • 2. tarjan算法
      • 3. kosaraju算法

一、拓扑排序题目

最近在看一个算法课程的时候看到了一个比较好玩的题目的扩展,它的原题如下:
在这里插入图片描述
对应的LeetCode题目为 207. 课程表

这个题目本身来说比较简单,就是一道标准的拓扑排序题目,解法代码如下:

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

public class Solution {
    public boolean scheduleCourses(int[][] prerequisites) {
        // 记录每个节点的入度
        int[] degree = new int[prerequisites.length]; 
        // 记录每个节点是哪些节点的前置节点
        List<Integer>[] neighbors = new List[prerequisites.length]; 
        // 记录当前可以选择的节点
        Queue<Integer> available = new LinkedList<>();

        for (int i = 0; i < prerequisites.length; i++) {
            degree[i] = prerequisites[i].length;
            neighbors[i] = new ArrayList<>();
            if (degree[i] == 0) {
                available.offer(i);
            }
        }

        for (int i = 0; i < prerequisites.length; i++) {
            for (int to : prerequisites[i]) {
                neighbors[to].add(i);
            }
        }

        int count = 0;
        while (!available.isEmpty()) {
            // 1. 取出available中一个节点
            // 2. 遍历该节点的邻居节点
            //   a. 将该邻居节点的入度减一
            //   b. 若此时邻居节点的入度为0,加入available中
            // 3. 处理节点数count加一
            int cur = available.poll();
            for (int to: neighbors[cur]) {
                degree[to]--;
                if (degree[to] == 0) {
                    available.offer(to);
                }
            }
            count++;
        }
        return count == prerequisites.length;
    }
}

二、题目拓展

这道题目整体难度不大,但是课程里提出了对于该题目的拓展非常有意思。
题目拓展:假如学生有同时上多门课的能力,那么是否可以根据他最多能同时上课的数量,来判断对于指定的课程安排他是否可以完成。

1. 思路分析

初看这个拓展时,我的想法是在有向图里找环的方式来实现,比如找到整个有向图中包含节点数目最多的环,判断这个数目是否超过了该同学最多能同时上课的数量。但这种方式有一个比较大的问题,就是如何定义什么是环,以及该定义下的环是否满足需要。根据这两个问题,我举出了如下两个图:
在这里插入图片描述
在这里插入图片描述
可以看到,对于第一个图来说,它可以说包含3个环,这种情况下该以哪个环的节点数来度量呢?对于第二个图,两个环共用了一个节点,此时只计算一个环的节点数并不能满足题目的需求。

此时仔细观察图二中的这种场景,我发现只有这两个环都计算节点数然后与可以最大同时上课数比较才成立。结合图一,可以得出一个结论,即当图中一个节点与另一个节点可以互相到达时,它们需要被计算在一起。这不正好就是有向图的强连通分量的定义吗?

于是,这个问题就转换成了,求一个有向图中包含节点数最大的连通分量的节点数。整个思路豁然开朗了。(由此可以看出,拿到一些特化的问题时把问题迁移到一种常识性问题是非常重要的!)

根据这种思路,我们需要求有向图中规模最大的连通分量的节点数,并且把它和学生最大同时上课数进行比较,就可以得到答案了。

详细图示如下:
在这里插入图片描述
将每一个强连通分量视为学生需要同时上的课程,即可以得到一个强连通分量缩点的拓扑排序,之后学生可以按照正常的拓扑排序顺序对缩点进行上课即可,如下所示:
在这里插入图片描述

求解有向图中的强连通分量问题一般有两种算法,tarjan算法和kosaraju算法,此处不赘述两种算法的细节,感兴趣的可以自行搜索,此处只把各自解法列在下方。

2. tarjan算法

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;

public class TarjanSolution {
    // 图的邻接表表示形式,记录每个节点是哪些节点的前置节点
    private List<Integer>[] neighbors;
    private int skill;
    
    private int[] dfn;
    private int[] low;
    private int idx;

    private Stack<Integer> stack;
    private boolean[] isInStack; // 用于快速判断是否在栈中
    public boolean scheduleCourses(int[][] prerequisites, int skill) {
        if (skill < 1) {
            return false;
        }
        this.skill = skill;
        // 初始化相关存储结构
        initGraph(prerequisites);

        // 最大连通分量的节点数
        return tarjan();
    }

    private void initGraph(int[][] prerequisites) {
        neighbors = new List[prerequisites.length]; 
        for (int i = 0; i < prerequisites.length; i++) {
            neighbors[i] = new LinkedList<>();
        }
        for (int i = 0; i < prerequisites.length; i++) {
            for (int to : prerequisites[i]) {
                neighbors[to].add(i);
            }
        }
    }

    private boolean tarjan() {
        this.dfn = new int[neighbors.length];
        this.low = new int[neighbors.length];
        this.idx = 0;
        this.isInStack = new boolean[neighbors.length];
        this.stack = new Stack<Integer>();

        for (int i = 0; i < neighbors.length; ++i) {
            if (dfn[i] == 0) {
                if (!tarjanRecursion(i)) { // 如果已经失败,则提前结束
                    return false;
                }
            }
        }
        return true;
    }

    private boolean tarjanRecursion(int cur) {
        // 入栈
        stack.push(cur);
        isInStack[cur] = true;

        //初始化当前节点的时间戳
        dfn[cur] = low[cur] = ++idx;

        // 遍历当前节点的邻居节点,共3类:1. 没被找过的;2. 在栈里的;3. 已经构成联通分量的(这种直接跳过即可)
        for (int neighbor: neighbors[cur]) {
            // 如果没被找过
            if (dfn[neighbor] == 0) {
                if (!tarjanRecursion(neighbor)) { // 如果已经失败,则提前结束
                    return false;
                }
                low[cur] = Math.min(low[cur], low[neighbor]);
            } else if (isInStack[neighbor]) { // 在栈里
                low[cur] = Math.min(low[cur], dfn[neighbor]);
            }
        }

        int connectedComponentNodeNum = 0;
        // 若dfn==low,则当前已找到一个强连通分量,该分量节点为当前节点到栈顶的所有节点
        if (dfn[cur] == low[cur]) {
            while (cur != stack.peek()) { // 将所有非当前节点退栈
                int tmp = stack.pop();
                isInStack[tmp] = false;
                if (++connectedComponentNodeNum > skill) {
                    return false;
                }
                
            }
            // 把当前节点退栈
            stack.pop();
            isInStack[cur] = false;
            if (++connectedComponentNodeNum > skill) {
                return false;
            }
        }
        return true;
    }
}

3. kosaraju算法

import java.util.LinkedList;
import java.util.List;
import java.util.Stack;

public class KosarajuSolution {
    // 图的邻接表表示形式,记录每个节点是哪些节点的前置节点
    private List<Integer>[] neighbors;
    // 图的逆邻接表表示形式,记录每个节点是哪些节点的后置节点
    private List<Integer>[] rneighbors;
    private int skill;

    private boolean[] visited;
    private Stack<Integer> stack;

    public boolean scheduleCourses(int[][] prerequisites, int skill) {
        if (skill < 1) {
            return false;
        }
        this.skill = skill;
        // 初始化相关存储结构
        initGraph(prerequisites);

        // 最大连通分量的节点数
        return kosaraju();
    }

    private void initGraph(int[][] prerequisites) {
        neighbors = new List[prerequisites.length]; 
        rneighbors = new List[prerequisites.length]; 
        for (int i = 0; i < prerequisites.length; i++) {
            neighbors[i] = new LinkedList<>();
            rneighbors[i] = new LinkedList<>();
        }
        for (int i = 0; i < prerequisites.length; i++) {
            for (int to : prerequisites[i]) {
                neighbors[to].add(i);
                rneighbors[i].add(to);
            }
        }
    }

    private boolean kosaraju() {
        this.visited = new boolean[neighbors.length];
        this.stack = new Stack<Integer>();
        for (int i = 0; i < neighbors.length; ++i) { // 遍历正向图,记录出栈顺序
            if (!this.visited[i]) {
                kosarajuDfs1(i);
            }
        }
        while (!stack.isEmpty()) { // 从出栈最晚的节点开始,dfs遍历反向图
            int cur = stack.pop();
            if (this.visited[cur]) {
                if (kosarajuDfs2(cur) > skill) // 提前结束
                    return false;
            }
        }
        return true;
    }

    private void kosarajuDfs1(int cur) {
        this.visited[cur] = true;
        for (int next: this.neighbors[cur]) {
            if (!this.visited[next]) {
                kosarajuDfs1(next);
            }
        }
        stack.push(cur);
    }

    private int kosarajuDfs2(int cur) {
        this.visited[cur] = false;
        int count = 1;
        for (int pre: this.rneighbors[cur]) {
            if (this.visited[pre]) {
                if (count > this.skill) return count; // 提前结束
                count += kosarajuDfs2(pre);
            }
        }
        return count;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1306152.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C# WPF上位机开发(增强版绘图软件)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 前面我们写过一个绘图软件&#xff0c;不过那个比较简单&#xff0c;主要就是用鼠标模拟pen进行绘图。实际应用中&#xff0c;另外一种使用比较多的…

【flutter对抗】blutter使用+ACTF习题

最新的能很好反编译flutter程序的项目 1、安装 git clone https://github.com/worawit/blutter --depth1​ 然后我直接将对应的两个压缩包下载下来&#xff08;通过浏览器手动下载&#xff09; 不再通过python的代码来下载&#xff0c;之前一直卡在这个地方。 如果读者可以…

C++11 【初识】

C11简介 1.在2003年C标准委员会曾经提交了一份技术勘误表(简称TC1)&#xff0c;使得C03这个名字已经取代了C98称为C11之前的最新C标准名称。 2.不过由于C03(TC1)主要是对C98标准中的漏洞进行修复&#xff0c;语言的核心部分则没有改动&#xff0c;因此人们习惯性的把两个标准合…

【vtkWidgetRepresentation】第十期 vtkAngleRepresentation标注角度

很高兴在雪易的CSDN遇见你 前言 本文分享VTK中的角度标注,希望对各位小伙伴有所帮助! 感谢各位小伙伴的点赞+关注,小易会继续努力分享,一起进步! 你的点赞就是我的动力(^U^)ノ~YO AngleRepresentation 目录 前言 1. vtkAngleRepresentatio

Windows使用selenium操作浏览器爬虫

以前的大部分程序都是操作Chrome&#xff0c;很少有操作Edge&#xff0c;现在以Edge为例。 Selenium本身是无法直接控制浏览器的&#xff0c;不同的浏览器需要不同的驱动程序&#xff0c;Google Chrome需要安装ChromeDriver、Edge需要安装Microsoft Edge WebDriver&#xff0c…

直面双碳目标,优维科技携手奥意建筑打造绿色低碳建筑数智云平台

优维“双碳”战略合作建筑 为落实创新驱动发展战略&#xff0c;增强深圳工程建设领域科技创新能力&#xff0c;促进技术进步、科技成果转化和推广应用&#xff0c;根据《深圳市工程建设领域科技计划项目管理办法》《深圳市住房和建设局关于组织申报2022年深圳市工程建设领域科…

二叉搜索树基本概念与实现

目录 基本概念 模拟实现 完整代码 基本概念 根的左节点比根小 根的右节点比根大 左右子树都满足 搜索二叉树的中序遍历是升序 模拟实现 完整代码 #pragma oncetemplate<class K> struct BSNode {BSNode<K>* _left;BSNode<K>* _right;K _val;BSNode(c…

【C语言程序设计】循环结构程序设计

目录 前言 一、程序设计第一题 二、程序设计第二题 三、程序设计第三题 总结 &#x1f308;嗨&#xff01;我是Filotimo__&#x1f308;。很高兴与大家相识&#xff0c;希望我的博客能对你有所帮助。 &#x1f4a1;本文由Filotimo__✍️原创&#xff0c;首发于CSDN&#x1f4da…

1.函数递归起(复习)

1.debug版本可以调试,realse版本不能调试 2.在realse版本中,代码已经得到了优化(编译器可能会自作主张地对代码进行优化),在大小和速度上都是最优的 3.ctrl F5 是开始执行不调试 4.设置好断点后,用F5到达该断点,相当于是到达了该断点的那个位置程序就先停止运行了 5.设立断…

Linux shell编程学习笔记35:seq

0 前言 在使用 for 循环语句时&#xff0c;我们经常使用到序列。比如&#xff1a; for i in 1 2 3 4 5 6 7 8 9 10; do echo "$i * 2 $(expr $i \* 2)"; done 其中的 1 2 3 4 5 6 7 8 9 10;就是一个整数序列 。 为了方便我们使用数字序列&#xff0c;Linux提供了…

UDS DTC状态掩码/DTC状态位

文章目录 简介用途依赖知识1、测试&#xff08;test&#xff09;2、操作循环&#xff08;operation cycle&#xff09;3、老化&#xff08;aging&#xff09; DTC状态位1、Bit 0&#xff1a;Test Failed2、Bit 1&#xff1a;Test Failed This operation cycle3、Bit 2&#xff…

【大数据】Hadoop生态未来发展的一些看法

大数据的起源 谷歌在2003到2006年间发表了三篇论文&#xff0c;《MapReduce: Simplified Data Processing on Large Clusters》&#xff0c;《Bigtable: A Distributed Storage System for Structured Data》和《The Google File System》介绍了Google如何对大规模数据进行存储…

基于51单片机的语音识别控制系统

0-演示视频 1-功能说明 &#xff08;1&#xff09;使用DHT11检测温湿度&#xff0c;然后用LCD12864显示&#xff0c;语音播放&#xff0c;使用STC11l08xe控制LD3320做语音识别&#xff0c; &#xff08;2&#xff09;上电时语音提示&#xff1a;欢迎使用声音识别系统&#xf…

数据结构篇-顺序表及单项链表

目录 一、学习目标 二、顺序表 1. 线性表 1.1 概念 1.2 举例 2. 顺序表 2.1 基本概念 2.2 基本操作 2.3 顺序表优缺点总结 三、单项链表 1. 基本概念 2. 链表的分类 无头节点&#xff1a; 有头节点&#xff1a; 增添加节点 查找节点 删除节点 链表遍历 销毁链…

【ARM Trace32(劳特巴赫) 使用介绍 13 -- Trace32 断点 Break 命令篇】

文章目录 1. Break.Set1.1 TRACE32 Break1.1.1 Break命令控制CPU的暂停1.2 Break.Set 设置断点1.2.1 Trace32 程序断点1.2.2 读写断点1.2.2.1 变量被改写为特定值触发halt1.2.2.2 设定非值触发halt1.2.2.4 变量被特定函数改写触发halt1.2.3 使用C/C++语法设置断点条件1.2.4 使用…

折点计数 C语言xdoj46

问题描述 给定n个整数表示一个商店连续n天的销售量。如果某天之前销售量在增长&#xff0c;而后一天销售量减少&#xff0c;则称这一天为折点&#xff0c;反过来如果之前销售量减少而后一天销售量增长&#xff0c;也称这一天为折点&#xff0c;其他的天都不是折点。如图…

AI大模型行业2024年上半年投资策略:大模型多模态化趋势显著,AI应用侧加速繁华

今天分享的AI系列深度研究报告&#xff1a;《AI大模型行业2024年上半年投资策略&#xff1a;大模型多模态化趋势显著&#xff0c;AI应用侧加速繁华》。 &#xff08;报告出品方&#xff1a;东莞证券&#xff09; 报告共计&#xff1a;30页 1.传媒行业行情和业绩回顾 1.1行业…

数据可视化:解析跨行业普及之道

数据可视化作为一种强大的工具&#xff0c;在众多行业中得到了广泛的应用&#xff0c;其价值和优势不断被发掘和利用。今天就让我以这些年来可视化设计的经验&#xff0c;讨论一下数据可视化在各个行业中备受青睐的原因吧。 无论是商业、科学、医疗保健、金融还是教育领域&…

spring 笔记一 spring快速入门和配置文件详解

Spring简介 Spring是分层的 Java SE/EE应用full-stack 轻量级开源框架&#xff0c;以 IoC&#xff08;Inverse Of Control&#xff1a;反转控制&#xff09;和AOP&#xff08;Aspect Oriented Programming&#xff1a;面向切面编程&#xff09;为内核。 提供了展现层SpringMV…

如何FL Studio显示中文?切换语言教程

你是不是也在为fl studio的英文界面而苦恼&#xff1f;你是不是也想让你的fl studio 说中文&#xff0c;方便你制作音乐&#xff1f;你是不是也在网上找了很多教程&#xff0c;却发现都是复杂的&#xff0c;或者已经过时的&#xff1f;如果你的答案是肯定的&#xff0c;那么你来…