2022.3.24 图论——拓扑排序算法

article/2025/10/1 7:27:35

文章目录

  • 一、拓扑排序简介
  • 二、例题
    • 1.题目
    • 2.分析
    • 3.代码


一、拓扑排序简介

1.Topological Sorting,指的是一个DAG(Directed Acyclic Graph)即有向图所有顶点满足一定条件的线性序列。

拓扑序列应满足两个条件:

每个点都只出现一次
如果存在一条从A指向B的边,那么A点在序列中出现的顺序应该在B点之前。

在这里插入图片描述
2.算法原理:

①从 DAG 图中选择一个 没有前驱(即入度为0)的顶点并输出。
②从图中删除该顶点和所有以它为起点的有向边。
③重复 1 和 2 直到当前的 DAG 图为空或当前图中不存在无前驱的顶点为止。

在这里插入图片描述
得到拓扑排序后的结果是 { 1, 2, 4, 3, 5 }

3.注意事项:
①非DAG图没有拓扑排序一说
②拓扑排序的情况可能是不唯一的


二、例题

1.题目

  1. 课程表 II

跳转LeetCode

现在你总共有 numCourses 门课需要选,记为 0 到 numCourses - 1。给你一个数组 prerequisites ,其中 prerequisites[i] = [ai, bi] ,表示在选修课程 ai 前 必须 先选修 bi 。

例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示:[0,1] 。
返回你为了学完所有课程所安排的学习顺序。可能会有多个正确的顺序,你只要返回 任意一种 就可以了。如果不可能完成所有课程,返回 一个空数组 。


示例 1:
输入:numCourses = 2, prerequisites = [[1,0]]
输出:[0,1]
解释:总共有 2 门课程。要学习课程 1,你需要先完成课程 0。因此,正确的课程顺序为 [0,1] 。

示例 2:
输入:numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]]
输出:[0,2,1,3]
解释:总共有 4 门课程。要学习课程 3,你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。
因此,一个正确的课程顺序是 [0,1,2,3] 。另一个正确的排序是 [0,2,1,3] 。

示例 3:
输入:numCourses = 1, prerequisites = []
输出:[0]


2.分析

参考文章

①DFS形式:
课程之间的依赖关系抽象成有向边,那么这幅图的拓扑排序结果就是上课顺序。

首先,我们先判断一下题目输入的课程依赖是否成环,成环的话是无法进行拓扑排序的,所以我们可以复用 “课程表I” 的主函数,之后将后序遍历的结果进行反转,就是拓扑排序的结果。

在编码层面,问题则转化为:由所给数组构建图 → 图的后序遍历 → 判断图中是否存在环 → 翻转后序遍历序列

②BFS形式:

1、构建邻接表,和之前一样,边的方向表示「被依赖」关系。
2、构建一个 indegree 数组记录每个节点的入度,即 indegree[i] 记录节点 i 的入度。以及用于存储从图中被弹出的节点的数组 ans。
3、对 BFS 队列进行初始化,将入度为 0 的节点首先装入队列。
4、开始执行 BFS 循环,不断弹出队列中的节点,加入 ans 中,减少相邻节点的入度,并将入度变为 0 的节点加入队列。
5、如果最终所有节点都被遍历过(count 等于节点数),则说明不存在环,反之则说明存在环。
6、若不存在环,则可直接返回数组。

3.代码

①DFS答案代码:

package Graph;import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;/*** @author: LYZ* @date: 2022/3/24 18:51* @description: 210. 课程表 II*/
public class FindOrder {public static void main(String[] args) {int numCourses = 4;int[][] prerequisites = {{1, 0}, {2, 0}, {3, 1}, {3, 2}};FindOrder findOrder = new FindOrder();int[] ans = findOrder.findOrder(numCourses, prerequisites);for (int n : ans) {System.out.println(n);}}public int[] findOrder(int numCourses, int[][] prerequisites) {List<Integer>[] graph = buildGraph(numCourses, prerequisites);visited = new boolean[numCourses];onPath = new boolean[numCourses];for (int i = 0; i < numCourses; i++) {traverse(graph, i);}//以下是新添语句// 有环图无法进行拓扑排序if (hasCycle) {return new int[]{};}//反转后序遍历结果即为拓扑排序结果Collections.reverse(postorder);int[] ans = new int[numCourses];for (int i = 0; i < numCourses; i++) {ans[i] = postorder.get(i);}return ans;}List<Integer>[] buildGraph(int numCourses, int[][] prerequisites) {// 图中共有 numCourses 个节点List<Integer>[] graph = new LinkedList[numCourses];for (int i = 0; i < numCourses; i++) {graph[i] = new LinkedList<>();}for (int[] edge : prerequisites) {int from = edge[1], to = edge[0];// 添加一条从 from 指向 to 的有向边// 边的方向是「被依赖」关系,即修完课程 from 才能修课程 tograph[from].add(to);}return graph;}List<Integer> postorder = new ArrayList<>();boolean[] visited;boolean[] onPath;boolean hasCycle = false;void traverse(List<Integer>[] graph, int s) {if (onPath[s]) {hasCycle = true;}if (visited[s] || hasCycle) {return;}visited[s] = true;onPath[s] = true;for (int n : graph[s]) {traverse(graph, n);}postorder.add(s); //相较于课程表I增加的语句onPath[s] = false;}
}

②BFS答案代码:
package Graph;import java.util.*;/*** @author: LYZ* @date: 2022/3/24 18:51* @description: 210. 课程表 II*/
public class FindOrder {public static void main(String[] args) {int numCourses = 4;int[][] prerequisites = {{1, 0}, {2, 0}, {3, 1}, {3, 2}};FindOrder findOrder = new FindOrder();int[] ans = findOrder.findOrder(numCourses, prerequisites);for (int n : ans) {System.out.println(n);}}//BFS法public int[] findOrder(int numCourses, int[][] prerequisites) {// 建图,有向边代表「被依赖」关系List<Integer>[] graph = buildGraph(numCourses, prerequisites);List<Integer> list = new ArrayList<>();int[] ans = new int[numCourses];// 构建入度数组int[] indegree = new int[numCourses];for (int[] edge : prerequisites) {int from = edge[1], to = edge[0];// 节点 to 的入度加一indegree[to]++;}// 根据入度初始化队列中的节点Queue<Integer> q = new LinkedList<>();for (int i = 0; i < numCourses; i++) {if (indegree[i] == 0) {// 节点 i 没有入度,即没有依赖的节点// 可以作为拓扑排序的起点,加入队列q.offer(i);}}// 记录遍历的节点个数int count = 0;// 开始执行 BFS 循环while (!q.isEmpty()) {// 弹出节点 cur,并将它指向的节点的入度减一int cur = q.poll();list.add(cur);count++;for (int next : graph[cur]) { //graph[cur]是由cur节点指向的所有节点构成的List集合indegree[next]--; //indegree是存储所有节点入度值的数组if (indegree[next] == 0) {// 如果入度变为 0,说明 next 依赖的节点都已被遍历q.offer(next);}}}if (count != numCourses) {return new int[]{};}for (int i = 0; i < numCourses; i++) {ans[i] = list.get(i);}return ans;}// 建图函数List<Integer>[] buildGraph(int numCourses, int[][] prerequisites) {// 图中共有 numCourses 个节点List<Integer>[] graph = new LinkedList[numCourses];for (int i = 0; i < numCourses; i++) {graph[i] = new LinkedList<>();}for (int[] edge : prerequisites) {int from = edge[1], to = edge[0];// 添加一条从 from 指向 to 的有向边// 边的方向是「被依赖」关系,即修完课程 from 才能修课程 tograph[from].add(to);}return graph;}
}

记录拓扑排序结果我处理的不好,额外声明了一个集合。题解的代码,利用用于判断图中是否有环的计数器,作为 ans 数组的下标,很巧妙。

    // 记录拓扑排序结果int[] res = new int[numCourses];// 记录遍历节点的顺序(索引)int count = 0;// 开始执行 BFS 算法while (!q.isEmpty()) {int cur = q.poll();// 弹出节点的顺序即为拓扑排序结果res[count] = cur;count++;for (int next : graph[cur]) {indgree[next]--;if (indgree[next] == 0) {q.offer(next);}}}

在这类题中, buildGraph 函数一直未变,要记熟练。


http://chatgpt.dhexx.cn/article/cEr5kOgk.shtml

相关文章

数据结构——图——拓扑排序算法

数据结构——图——拓扑排序算法 对AOV网进行拓扑排序的基本思路是:从AOV网中选择一个入度为0的顶点输出&#xff0c;然后删去此顶点&#xff0c;并删除以此顶点为尾的弧&#xff0c;继续重复此步骤&#xff0c;直到输出全部顶点或者AOV 网中不存在入度为0的顶点为止。 首先我…

拓扑排序详解(包含算法原理图解、算法实现过程详解、算法例题变式全面讲解等)

前置知识 有向无环图 在图论中&#xff0c;如果一个有向图无法从某个顶点出发经过若干条边回到该点&#xff0c;则这个图是一个有向无环图&#xff08;DAG图&#xff09;。 如图所示。 入度 对于一个有向图&#xff0c;若x点指向y点&#xff0c;则称x点为y点的入度。 出度…

拓扑排序算法分析(通俗易懂)

拓扑排序&#xff08;其实是一种依赖关系&#xff09;&#xff1a;对于有向且无环的图来说&#xff0c;当前这个节点的依赖来其之前已经完成了。 下面附上一个图让大伙更好的理解&#xff1a; 比如这个图&#xff1a;B需要依赖A才能完成&#xff0c;A需要依赖C和D才能完成&…

拓扑排序算法详讲

经过一天的专研,终于明白了拓扑排序算法,写篇博客记录一下心得. 一.拓扑排序介绍 在一个表示工程的有向图中,用顶点表示活动,用弧表示活动之间的优先关系,这样的有向图为顶点表示活动的网,我们称为AOV网. 设G(V,E)是一个具有n个顶点的有向图,v中的顶点序列v1,v2…,vn,满足若从…

C++ 拓扑排序算法

拓扑排序 有向无环图 如果一个有向图的任意顶点都无法通过一些有向边回到自身&#xff0c;那么称这个有向图为有向无环图。 拓扑排序 拓扑排序是将有向无环图G的所有顶点排成一个线性序列&#xff0c;使得对图G中的任意两个顶点u、v&#xff0c;如果存在边u->v&#xff0c;那…

拓扑排序

拓扑排序 一、拓扑排序的定义&#xff1a; 先引用一段百度百科上对于拓扑排序的定义&#xff1a; 对一个有向无环图 ( Directed Acyclic Graph 简称 DAG ) G 进行拓扑排序&#xff0c;是将 G 中所有顶点排成一个线性序列&#xff0c;使得图中任意一对顶点 u 和 v &#xff0c…

拓扑排序算法

拓扑排序介绍 拓扑排序(Topological Order)是指&#xff0c;将一个有向无环图(Directed Acyclic Graph简称DAG)进行排序进而得到一个有序的线性序列。 这样说&#xff0c;可能理解起来比较抽象。下面通过简单的例子进行说明&#xff01; 例如&#xff0c;一个项目包括A、B、C…

经典算法之拓扑排序

定义&#xff1a; 把AOV网&#xff08;用定点表示活动&#xff0c;用弧表示活动间优先关系的有向图&#xff09;络中各个顶点按照它们互相之间的优先关系排列成一个线性序列的过程叫做拓扑排序。 方法&#xff1a; 在有向图中选一个没有前驱的顶点并且输出从图中删除该顶点和…

拓扑排序(topological sorting)介绍及Python实现

目录 1. 拓扑排序 2. 拓扑排序存在的前提 3. 拓扑排序的唯一性问题 4. 拓扑排序算法原理 4.1 广度优先遍历 4.2 深度优先遍历 5. 代码实现 5.1 Graph类的实现 5.2 广度优先搜索 5.3 深度优先搜索简易版&#xff08;无loop检测&#xff09; 5.4 深度优先搜索完整版 …

【算法】拓扑排序

今天学习拓扑排序。如果一个有向图的任意顶点都无法通过一些有向边回到自身&#xff0c;那么称这个有向图为有向无环图&#xff08;Directed Acyclic Graph&#xff0c;DAG&#xff09;。拓扑排序就是将有向无环图的所有顶点排序&#xff0c;使得图中任意两个点 u、v&#xff0…

拓扑排序及算法实现

一、拓扑排序概念 对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序&#xff0c;是将G中所有顶点排成一个线性序列&#xff0c;使得图中任意一对顶点u和v&#xff0c;若边<u,v>∈E(G)&#xff0c;则u在线性序列中出现在v之前。通常&#xff0c;这样的线性…

利用迭代法求平方根——迭代法开平方运算

一、题目 用迭代法求&#xff1a; 的值。要求精度为0.00001&#xff0c;即 二、迭代公式 求平方根的迭代公式为&#xff1a; 当满足 时&#xff0c;这时的即是求得的根。如果 得到是精确值。如果不为0&#xff0c;则是近似值。 三、C代码 先给出开平方运算的函数。再给出主…

【算法】牛顿迭代法求平方根的原理和误差分析

前言 在《算法(第四版)》中的P23页&#xff0c;给出了经典的利用牛顿迭代法求平方根的算法&#xff0c;牛顿迭代法在数值计算中应用十分广泛&#xff0c;但是在看书中的代码时&#xff0c;我最困惑的是其中对收敛条件的判断&#xff0c;经过查阅资料和论坛&#xff0c;找到…

使用迭代法来求a的平方根

今天朋友问我一道使用迭代法求a的平方根的题&#xff0c;感觉受益匪浅&#xff0c;与诸君相分享 首先我们来看一下题目 我们也无需了解迭代法是什么原理&#xff0c;根据这个题目可以分析得到&#xff0c;需要使用while循环&#xff0c;下面是我的代码实践 #define _CRT_SEC…

利用牛顿迭代法求平方根

求n的平方根&#xff0c;先假设一猜测值X0 1&#xff0c;然后根据以下公式求出X1&#xff0c;再将X1代入公式右边&#xff0c;继续求出X2…通过有效次迭代后即可求出n的平方根&#xff0c;Xk1 先让我们来验证下这个巧妙的方法准确性&#xff0c;来算下2的平方根 (Computed b…

牛顿迭代法求解平方根

一个实例迭代简介牛顿迭代法 牛顿迭代法简介简单推导泰勒公式推导延伸与应用 一个实例 //java实现的sqrt类和方法 public class sqrt {public static double sqrt(double n){if (n<0) return Double.NaN;double err 1e-15;double t n;while (Math.abs(t - n/t) > err…

牛顿迭代法求一个数的平方根(python)

# !/usr/bin/env python # -*- coding: utf-8 -*- """ Author: P♂boy License: (C) Copyright 2013-2017, Node Supply Chain Manager Corporation Limited. Contact: 17647361832163.com Software: Pycharm File: sqrt.py.py Time: 2018/11/19 16:22 Desc:牛顿…

c++迭代法求平方根

用迭代法求x &#xff0c;要求前后两次求出的x的差的绝对值小于10-5&#xff0c;求平方根的迭代公式为&#xff1a; 输入 一个数a。 输出 的值。 样例输入 2.0样例输出 The square root of 2.00 is 1.41421 这道题我无语了...&#xff1a; #include<bits/stdc.h> …

牛顿迭代法求平方根原理

牛顿迭代法可以求解n次方的根&#xff0c;但这里只讨论用它来求平方根。 牛顿迭代法求平方根过程 Java代码实现 /*** 求一个数的平方根* param number* return*/public static double squareRoot(double number){if(number < 0){ //小于0的数无法开平方return Double.NaN;}e…

【算法】牛顿迭代法求平方根及多次方根

1. 概述 牛顿迭代法 牛顿迭代法 牛顿迭代法 是非常高效的求解方程的根的方法。其求解原理可以参考各文献。大体的思路如下&#xff1a; 通过不断地做切线来逼近真实的根&#xff0c;直到误差小于精度。 可得迭代公式&#xff1a; x n 1 x n − f ( x n ) f ′ ( x n ) x_{…