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

本文主要是介绍【LeetCode题目拓展】第207题 课程表 拓展(拓扑排序、Tarjan算法、Kosaraju算法),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

    • 一、拓扑排序题目
    • 二、题目拓展
      • 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;}
}

这篇关于【LeetCode题目拓展】第207题 课程表 拓展(拓扑排序、Tarjan算法、Kosaraju算法)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/486856

相关文章

Python中的随机森林算法与实战

《Python中的随机森林算法与实战》本文详细介绍了随机森林算法,包括其原理、实现步骤、分类和回归案例,并讨论了其优点和缺点,通过面向对象编程实现了一个简单的随机森林模型,并应用于鸢尾花分类和波士顿房... 目录1、随机森林算法概述2、随机森林的原理3、实现步骤4、分类案例:使用随机森林预测鸢尾花品种4.1

Python中lambda排序的六种方法

《Python中lambda排序的六种方法》本文主要介绍了Python中使用lambda函数进行排序的六种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们... 目录1.对单个变量进行排序2. 对多个变量进行排序3. 降序排列4. 单独降序1.对单个变量进行排序

关于Java内存访问重排序的研究

《关于Java内存访问重排序的研究》文章主要介绍了重排序现象及其在多线程编程中的影响,包括内存可见性问题和Java内存模型中对重排序的规则... 目录什么是重排序重排序图解重排序实验as-if-serial语义内存访问重排序与内存可见性内存访问重排序与Java内存模型重排序示意表内存屏障内存屏障示意表Int

哈希leetcode-1

目录 1前言 2.例题  2.1两数之和 2.2判断是否互为字符重排 2.3存在重复元素1 2.4存在重复元素2 2.5字母异位词分组 1前言 哈希表主要是适合于快速查找某个元素(O(1)) 当我们要频繁的查找某个元素,第一哈希表O(1),第二,二分O(log n) 一般可以分为语言自带的容器哈希和用数组模拟的简易哈希。 最简单的比如数组模拟字符存储,只要开26个c

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

康拓展开(hash算法中会用到)

康拓展开是一个全排列到一个自然数的双射(也就是某个全排列与某个自然数一一对应) 公式: X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0! 其中,a[i]为整数,并且0<=a[i]<i,1<=i<=n。(a[i]在不同应用中的含义不同); 典型应用: 计算当前排列在所有由小到大全排列中的顺序,也就是说求当前排列是第

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig

usaco 1.3 Mixing Milk (结构体排序 qsort) and hdu 2020(sort)

到了这题学会了结构体排序 于是回去修改了 1.2 milking cows 的算法~ 结构体排序核心: 1.结构体定义 struct Milk{int price;int milks;}milk[5000]; 2.自定义的比较函数,若返回值为正,qsort 函数判定a>b ;为负,a<b;为0,a==b; int milkcmp(const void *va,c