算法學習筆記:29.拓撲排序——從原理到實戰,涵蓋 LeetCode 與考研 408 例題

拓撲排序(Topological Sorting)是一種針對有向無環圖(DAG)的線性排序算法,它將圖中的頂點按照一定規則排列,使得對于圖中的任意一條有向邊 u→v,頂點 u 都排在頂點 v 之前。拓撲排序在任務調度、課程安排、編譯依賴等場景中有著廣泛應用。


拓撲排序的基本概念與適用場景

核心定義

  • 有向無環圖(DAG):頂點間的邊有方向,且不存在環路的圖。拓撲排序僅適用于 DAG,若圖中存在環,則不存在拓撲排序。
  • 拓撲序:對于 DAG 中的頂點,存在一個線性序列 v?, v?, ..., v?,使得所有有向邊 v?→v? 均滿足 i < j。一個 DAG 可能存在多個拓撲序。

應用場景

  • 任務調度:若任務 B 依賴任務 A,則 A 必須在 B 之前執行,拓撲排序可確定任務執行順序。
  • 課程安排:大學課程存在先修關系(如 “數據結構” 需先修 “C 語言”),拓撲排序可生成合理的選課順序。
  • 編譯依賴:編譯器處理源文件時,需按依賴關系(如頭文件包含)確定編譯順序。

DAG 與拓撲序示例

拓撲排序的核心算法

Kahn算法(基于入度與隊列)

核心思想:通過維護節點的入度(指向該節點的邊數),逐步選出入度為0的節點(無前置依賴),并減少其鄰接節點的入度,直至所有節點被處理或檢測到環。

算法步驟

1. 計算入度:遍歷圖,記錄每個節點的入度。

2. 初始化隊列:將所有入度為0的節點加入隊列。

3. 生成拓撲序

- 出隊一個節點,加入拓撲序。

- 對該節點的所有鄰接節點,入度減1;若入度變為0,入隊。

4. 檢測環:若拓撲序長度小于節點總數,則圖中存在環,無拓撲序。

基于DFS的拓撲排序

核心思想:通過深度優先搜索,在回溯時將節點加入拓撲序(逆后序遍歷),若搜索中遇到回邊(指向已訪問但未回溯的節點),則存在環。

算法步驟

1. 標記訪問狀態:用三個狀態標記節點:未訪問(0)、訪問中(1)、已訪問(2)。

2. 遞歸DFS

- 若節點狀態為訪問中,檢測到環。

- 若節點未訪問,標記為訪問中,遞歸遍歷所有鄰接節點。

- 回溯時,標記節點為已訪問,加入拓撲序。

3. 逆序輸出:拓撲序為逆后序遍歷結果。

LeetCode例題實戰

例題1:207. 課程表(中等)

題目描述:你這個學期必須選修 `numCourses` 門課程,記為 `0` 到 `numCourses - 1`。給你一個數組 `prerequisites`,其中 `prerequisites[i] = [ai, bi]`,表示在選修課程 `ai` 之前必須先選修 `bi`。判斷是否可能完成所有課程的學習。

示例

輸入:numCourses = 2, prerequisites = [[1,0]]

輸出:true(先學 0,再學 1)

輸入:numCourses = 2, prerequisites = [[1,0],[0,1]]

輸出:false(存在環 0→1→0)

解題思路(Kahn算法)

1. 構建圖:用鄰接表表示課程依賴關系,數組 `inDegree` 記錄每個課程的入度。

2. 初始化隊列:將入度為0的課程入隊。

3. 拓撲排序:處理隊列中的課程,減少依賴它的課程的入度,統計已處理課程數。

4. 判斷結果:若已處理課程數等于 `numCourses`,則無環,可完成;否則有環,不可完成。

代碼實現
import java.util.*;class Solution {public boolean canFinish(int numCourses, int[][] prerequisites) {// 1. 構建鄰接表和入度數組List<List<Integer>> adj = new ArrayList<>();int[] inDegree = new int[numCourses];for (int i = 0; i < numCourses; i++) {adj.add(new ArrayList<>());}for (int[] p : prerequisites) {int course = p[0];int pre = p[1];adj.get(pre).add(course); // pre → courseinDegree[course]++;}// 2. 初始化隊列(入度為0的課程)Queue<Integer> queue = new LinkedList<>();for (int i = 0; i < numCourses; i++) {if (inDegree[i] == 0) {queue.offer(i);}}// 3. 拓撲排序int count = 0; // 已處理的課程數while (!queue.isEmpty()) {int curr = queue.poll();count++;// 處理依賴curr的課程for (int next : adj.get(curr)) {inDegree[next]--;if (inDegree[next] == 0) {queue.offer(next);}}}// 4. 判斷是否所有課程都被處理return count == numCourses;}}
復雜度分析
  • 時間復雜度:O (V + E),V 為節點數(課程數),E 為邊數(依賴關系數)。
  • 空間復雜度:O (V + E),鄰接表存儲圖,隊列存儲節點。

例題 2:210. 課程表 II(中等)

題目描述:同上題,但需返回一個可行的拓撲序;若無法完成所有課程,返回空數組。

示例

輸入:numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]]

輸出:[0,1,2,3] 或 [0,2,1,3] 等

解題思路

在 Kahn 算法中,將出隊的節點依次加入拓撲序列表,最后若列表長度等于課程數則返回,否則返回空數組。

代碼實現
import java.util.*;class Solution {public int[] findOrder(int numCourses, int[][] prerequisites) {// 1. 構建鄰接表和入度數組List<List<Integer>> adj = new ArrayList<>();int[] inDegree = new int[numCourses];for (int i = 0; i < numCourses; i++) {adj.add(new ArrayList<>());}for (int[] p : prerequisites) {int course = p[0];int pre = p[1];adj.get(pre).add(course);inDegree[course]++;}// 2. 初始化隊列Queue<Integer> queue = new LinkedList<>();for (int i = 0; i < numCourses; i++) {if (inDegree[i] == 0) {queue.offer(i);}}// 3. 拓撲排序并記錄結果List<Integer> topoOrder = new ArrayList<>();while (!queue.isEmpty()) {int curr = queue.poll();topoOrder.add(curr);for (int next : adj.get(curr)) {inDegree[next]--;if (inDegree[next] == 0) {queue.offer(next);}}}// 4. 檢查是否有環if (topoOrder.size() != numCourses) {return new int[0];}// 轉換為數組int[] result = new int[numCourses];for (int i = 0; i < numCourses; i++) {result[i] = topoOrder.get(i);}return result;}}
復雜度分析
  • 時間復雜度:O (V + E),同 Kahn 算法。
  • 空間復雜度:O (V + E),額外存儲拓撲序列表。

考研 408 例題解析

例題 1:概念辨析題(選擇題)

題目:下列關于拓撲排序的敘述中,錯誤的是( )。

A. 拓撲排序僅適用于有向無環圖(DAG)

B. 一個 DAG 的拓撲序是唯一的

C. Kahn 算法通過維護入度為 0 的節點生成拓撲序

D. 基于 DFS 的拓撲排序需檢測回邊以判斷是否有環

答案:B

解析

  • A 正確:有環圖不存在拓撲序。
  • B 錯誤:一個 DAG 可能有多個拓撲序(如示例中的 A→B→C 和 A→D→B)。
  • C 正確:Kahn 算法的核心是入度管理。
  • D 正確:DFS 中遇到回邊(訪問中狀態的節點)說明有環。

例題 2:算法設計題(408 高頻考點)

題目:已知一個有向圖用鄰接表表示,設計一個算法判斷該圖是否為 DAG,若為 DAG 則輸出其拓撲序,否則輸出 “有環”。要求使用 Kahn 算法,并分析時間復雜度。

解題思路
  1. 數據結構:鄰接表 adj 存儲圖,數組 inDegree 存儲入度。
  2. 算法步驟
    • 計算所有節點的入度。
    • 入度為 0 的節點入隊,初始化拓撲序列表。
    • 處理隊列,更新入度和拓撲序,統計處理節點數。
    • 若處理節點數等于總節點數,輸出拓撲序;否則輸出 “有環”。
代碼實現
import java.util.*;public class TopologicalSort {// 判斷是否為DAG并返回拓撲序,否則返回nullpublic List<Integer> isDAGAndTopoOrder(List<List<Integer>> adj, int n) {int[] inDegree = new int[n];// 計算入度for (int u = 0; u < n; u++) {for (int v : adj.get(u)) {inDegree[v]++;}}Queue<Integer> queue = new LinkedList<>();for (int i = 0; i < n; i++) {if (inDegree[i] == 0) {queue.offer(i);}}List<Integer> topoOrder = new ArrayList<>();while (!queue.isEmpty()) {int u = queue.poll();topoOrder.add(u);for (int v : adj.get(u)) {inDegree[v]--;if (inDegree[v] == 0) {queue.offer(v);}}}return topoOrder.size() == n ? topoOrder : null;}public static void main(String[] args) {// 示例:DAGint n = 4;List<List<Integer>> adj = new ArrayList<>();for (int i = 0; i < n; i++) {adj.add(new ArrayList<>());}adj.get(0).add(1);adj.get(0).add(2);adj.get(1).add(3);adj.get(2).add(3);TopologicalSort ts = new TopologicalSort();List<Integer> result = ts.isDAGAndTopoOrder(adj, n);if (result != null) {System.out.println("拓撲序:" + result); // 輸出:[0,1,2,3] 或類似} else {System.out.println("有環");}}}
復雜度分析
  • 時間復雜度:O (V + E),V 為節點數,E 為邊數。計算入度需 O (E),隊列操作和入度更新共 O (V + E)。
  • 空間復雜度:O (V + E),鄰接表占 O (E),隊列和拓撲序占 O (V)。

拓撲排序的擴展與應用

實際應用場景

  • 項目管理:微軟 Project 等工具用拓撲排序安排任務依賴關系。
  • 編譯系統:GCC 編譯器處理源文件依賴,按拓撲序編譯。
  • 任務調度:操作系統進程調度中,按資源依賴關系確定執行順序。
  • 課程平臺:MOOC 平臺推薦先修課程,生成學習路徑。

與其他圖算法的關聯

  • 強連通分量(SCC):有環圖的 SCC 可收縮為單個節點,形成 DAG 后再拓撲排序。
  • 關鍵路徑:在帶權 DAG 中,拓撲序可高效計算最長路徑(關鍵路徑)。
  • 最短路徑:DAG 的單源最短路徑可通過拓撲序在 O (V + E) 時間內求解。

考研 408 備考要點

  • 核心考點:拓撲排序的適用條件、Kahn 算法步驟、環檢測方法。
  • 重點掌握
  1. 鄰接表的構建與入度計算。
  2. Kahn 算法中隊列的作用及拓撲序生成邏輯。
  3. 拓撲排序與 DAG 的等價關系(存在拓撲序 ? 是 DAG)。
  • 常見錯誤
    • 忽略入度為 0 的節點初始隊列可能為空(直接判定有環,但需確認節點數是否為 0)。
    • 處理鄰接節點時漏減入度,導致算法錯誤。

總結

拓撲排序是處理有向無環圖(DAG)的核心算法,其 Kahn 算法和 DFS 算法各有優勢:Kahn 算法直觀易實現,適合求拓撲序;DFS 算法更簡潔,適合環檢測。本文通過 LeetCode 例題(課程表系列)展示了算法的實際應用,通過考研 408 例題鞏固了理論基礎,結合 SVG 圖示清晰呈現了算法流程。

掌握拓撲排序的關鍵在于:

  1. 理解 DAG 與拓撲序的一一對應關系。
  2. 熟練實現 Kahn 算法的入度管理和隊列操作。
  3. 能將實際問題抽象為 DAG,并用拓撲排序解決依賴關系問題。

在考研備考中,需重點關注算法的時間復雜度分析和環檢測邏輯,這不僅是考試重點,也是解決復雜圖問題的基礎。

希望本文能夠幫助讀者更深入地理解拓撲排序算法,并在實際項目中發揮其優勢。謝謝閱讀!


希望這份博客能夠幫助到你。如果有其他需要修改或添加的地方,請隨時告訴我。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/90583.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/90583.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/90583.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

利用Web3加密技術保障您的在線數據安全

在這個信息爆炸的數字化時代&#xff0c;保護個人和企業數據安全變得尤為重要。Web3技術以其去中心化和加密特性&#xff0c;為在線數據安全提供了新的解決方案。本文將探討Web3技術如何通過加密技術保障您的在線數據安全&#xff0c;并介紹如何有效利用這些技術。 什么是Web3技…

Vue實現el-checkbox單選并回顯選中

先說需求 我要在頁面進行checkbox單選并回顯 第一步先把基本的頁面寫好噢&#xff1a;vue代碼&#xff1a;別忘了寫change啊<el-form-item label"按鈕顏色:" prop"menuColor"><el-checkbox-group v-model"buttonColor" change"bin…

動態規劃--序列找優問題【1】

一、說明 動態規劃似乎針對問題很多&#xff0c;五花八門&#xff0c;似乎每一個問題都有一套具體算法。其實不是的&#xff0c;動態規劃只有兩類&#xff1a;1&#xff09;針對圖的路徑問題 2&#xff09;針對一個序列的問題。本篇講動態規劃針對序列的算法范例。 二、動態規劃…

獨家|百度副總裁尚國斌即將離職,此前統籌百度地圖;行業搜索及智能體業務總經理謝天轉崗IDG

百度人事再變動。作者|文昌龍編輯|楊舟據「市象」了解&#xff0c;近期&#xff0c;百度副總裁尚國斌即將離職。公開資料顯示&#xff0c;尚國斌2010年畢業于南開大學&#xff0c;2012年加入百度&#xff0c;先后在商業分析部、集團戰略辦、智能駕駛事業群工作。尚國斌同樣也在…

Qt 網絡編程進階:HTTP 客戶端實現

在 Qt 應用程序中&#xff0c;實現高性能、可靠的 HTTP 客戶端是常見需求。Qt 提供了豐富的網絡模塊&#xff0c;包括 QNetworkAccessManager、QNetworkRequest 和 QNetworkReply 等類&#xff0c;用于簡化 HTTP 通信。本文將深入探討 Qt 網絡編程中 HTTP 客戶端的進階實現&…

Python Requests-HTML庫詳解:從入門到實戰

一、庫簡介 Requests-HTML是Python中集網絡請求與HTML解析于一體的全能型庫&#xff0c;由知名開發者Kenneth Reitz團隊維護。它完美結合了Requests的易用性和Parsel的選擇器功能&#xff0c;并內置JavaScript渲染引擎&#xff0c;特別適合現代動態網頁抓取。最新版本&#xf…

基于springboot的小區車位租售管理系統

博主介紹&#xff1a;java高級開發&#xff0c;從事互聯網行業六年&#xff0c;熟悉各種主流語言&#xff0c;精通java、python、php、爬蟲、web開發&#xff0c;已經做了六年的畢業設計程序開發&#xff0c;開發過上千套畢業設計程序&#xff0c;沒有什么華麗的語言&#xff0…

Kafka 如何優雅實現 Varint 和 ZigZag 編碼

ByteUtils 是 Kafka 中一個非常基礎且核心的工具類。從包名 common.utils 就可以看出&#xff0c;它被廣泛用于 Kafka 的各個模塊中。它的主要職責是提供一套高效、底層的靜態方法&#xff0c;用于在字節緩沖區 (ByteBuffer)、字節數組 (byte[]) 以及輸入/輸出流 (InputStream/…

局域網 IP地址

很多童鞋搞不清楚局域網ip是什么? 什么是局域網 IP 地址? 局域網 IP 地址,也稱為 私有 IP 地址(Private IP Address),是用于在局域網內部標識設備的地址。這些地址不能直接在互聯網上被訪問,通常由路由器自動分配,用于設備之間的內部通信。 局域網 IP 地址的分類 根…

k8s的service、deployment、探針詳解

1.k8s組成圖2.service和deployment的流量轉發圖# Deployment 定義容器端口 apiVersion: apps/v1 kind: Deployment metadata:name: myapp spec:template:spec:containers:- name: nginximage: nginxports:- containerPort: 80 # 容器監聽 80name: http # 端口命名&…

【PostgreSQL教程】PostgreSQL中json類型與jsonb類型的區別

博主介紹:?全網粉絲23W+,CSDN博客專家、Java領域優質創作者,掘金/華為云/阿里云/InfoQ等平臺優質作者、專注于Java技術領域? 技術范圍:SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大數據、物聯網、機器學習等設計與開發。 感興趣的可…

牛客刷題記錄01

除2&#xff01; 目錄 除2&#xff01; 題目描述&#xff1a; ?編輯 題目解析&#xff1a; 代碼實現&#xff1a; 數組中兩個字符串的最小距離__牛客網 題目描述&#xff1a; 題目解析&#xff1a; 代碼實現&#xff1a; 除2&#xff01; 題目描述&#xff1a; 給一個…

Docker Compose UI遠程訪問教程:結合貝銳花生殼實現內網穿透

對于很多剛接觸Docker的用戶來說&#xff0c;命令行操作總帶著一絲“勸退感”。尤其是要在Windows上部署服務、開放端口、配置參數時&#xff0c;稍有不慎就容易出錯。有沒有辦法像網頁后臺一樣&#xff0c;用圖形界面來管理Docker項目呢&#xff1f;答案是&#xff1a;有&…

HF83311_VB1/HF83311Q_VB1:高性能USB HiFi音頻解碼器固件技術解析

引言隨著高品質音頻體驗需求的不斷增長&#xff0c;音頻解碼器固件的性能和功能成為決定音頻設備品質的關鍵因素。本文將介紹一款基于XMOS XU316技術的高性能USB HiFi音頻解碼器固件——HF83311_VB1/HF83311Q_VB1&#xff0c;這是一款專為USB HiFi音頻應用設計的軟件解決方案。…

[ComfyUI] -入門1-ComfyUI 是什么?比 Stable Diffusion WebUI 強在哪?

ComfyUI 是一個開源的、節點可視化界面,用于構建與執行 Stable Diffusion 圖像生成流程。它把復雜的生成過程拆解為許多“節點”(如提示編碼、采樣器、控制網絡等),用戶通過連接節點,就能自由編排工作流 。這種設計適合開發者與進階用戶,更便于微調、多分支與復用流程。 …

[python][flask]flask接受get或者post參數

在 Flask 中&#xff0c;可以通過 request 對象來獲取客戶端通過 GET 或 POST 方法發送的參數。以下是如何在 Flask 中接收 GET 和 POST 參數的詳細說明&#xff1a;1. 接收 GET 參數GET 請求的參數通常通過 URL 的查詢字符串傳遞。例如&#xff0c;對于 URL http://example.co…

Creo 模塊眾多,企業如何按需靈活分配許可證資源?

在數字化設計與智能制造深入發展的當下&#xff0c;企業 CAD/CAE 工具的精細化管理越來越重要。Creo&#xff0c;作為 PTC 旗下一體化 3D CAD 平臺&#xff0c;以其模塊化、可擴展的產品架構&#xff0c;廣泛應用于機械、裝備、汽車、航空航天等行業。其豐富的模塊庫覆蓋建模設…

【c++】提升用戶體驗:問答系統的交互優化實踐——關于我用AI編寫了一個聊天機器人……(12)

本期依舊使用豆包輔助完成代碼。從功能到體驗的轉變上個版本已經實現了問答系統的核心功能&#xff1a;基于 TF-IDF 算法的問題匹配和回答。它能夠讀取訓練數據&#xff0c;處理用戶輸入&#xff0c;并返回最相關的答案。但在用戶體驗方面還有很大提升空間。讓我們看看改進版做…

Android UI 控件詳解實踐

一、UI 開發基礎概念&#xff08;初學者必看&#xff09; 在學習具體控件前&#xff0c;先理解以下核心概念&#xff0c;能大幅降低后續學習難度&#xff1a; 1. View 與 ViewGroup 的關系 View&#xff1a;所有 UI 控件的基類&#xff08;如 Button、TextView&#xff09;&…

關于linux運維 出現高頻的模塊認知

一、Linux 基礎核心&#xff08;必掌握&#xff09;核心工具&#xff1a;Shell 腳本、Systemd、用戶權限管理、日志分析&#xff08;journalctl、rsyslog&#xff09;企業需求&#xff1a;中小型公司&#xff1a;需獨立完成系統部署、故障排查&#xff0c;對腳本開發&#xff0…