圖論(2)算法之拓撲排序介紹

目錄

一、什么是拓撲排序?

二、拓撲排序的算法實現

1 BFS算法實現

(1)算法思路

(2) 代碼實現(Java)

2 DFS算法實現

(1)算法思路

(2) 代碼實現(Java)

三、實戰用例:具有優先級的任務調度系統

1 問題描述

2 解決方案

3 Java 實現


一、什么是拓撲排序?

拓撲排序(Topological Sorting)是圖論中的一種排序方式,主要用于有向無環圖(DAG, Directed Acyclic Graph)

在拓撲排序中,如果圖中的邊 u → v 表示任務 u 必須在任務 v 之前完成,那么拓撲排序就返回一個任務執行順序,使得所有的先決任務都排在其后續任務之前。

如果圖中存在環(即不是DAG),就不存在合法的拓撲排序。

二、拓撲排序的算法實現

我們以無權圖(不含權重)為例,使用 Java 實現拓撲排序的兩種主流算法:

1 BFS算法實現

Kahn算法是一種經典的基于BFS(廣度優先搜索)的拓撲排序算法

(1)算法思路
  1. 統計每個節點的入度(被依賴的次數)。

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

  3. 從隊列中取出一個節點,將其加入拓撲排序結果。

  4. 遍歷該節點的所有鄰接節點,將鄰接節點的入度減 1;若入度變為 0,則加入隊列。

  5. 重復步驟 3 和 4,直到隊列為空。

  6. 如果結果中的節點數不等于圖的節點總數,則說明圖中存在環。

(2) 代碼實現(Java)
import java.util.*;
?
public class TopSort {public static List<String> topSortKahn(Map<String, List<String>> graph) {// 1. 計算每個節點的入度Map<String, Integer> indegree = new HashMap<>();for (String u : graph.keySet()) {// 確保所有節點都在 indegree 中indegree.putIfAbsent(u, 0);for (String v : graph.get(u)) {indegree.put(v, indegree.getOrDefault(v, 0) + 1);}}
?// 2. 所有入度為 0 的節點入隊Queue<String> queue = new LinkedList<>();for (Map.Entry<String, Integer> entry : indegree.entrySet()) {if (entry.getValue() == 0) {queue.offer(entry.getKey());}}
?// 3. 進行拓撲排序List<String> result = new ArrayList<>();while (!queue.isEmpty()) {String node = queue.poll();result.add(node);
?List<String> neighbors = graph.getOrDefault(node, new ArrayList<>());for (String neighbor : neighbors) {indegree.put(neighbor, indegree.get(neighbor) - 1);if (indegree.get(neighbor) == 0) {queue.offer(neighbor);}}}
?// 4. 如果排序結果數量 < 節點總數,說明有環if (result.size() != indegree.size()) {return null;}
?return result;}// 測試用例public static void main(String[] args) {Map<String, List<String>> graph1 = new HashMap<>();graph1.put("A", Arrays.asList("C"));graph1.put("B", Arrays.asList("C", "D"));graph1.put("C", Arrays.asList("E"));graph1.put("D", Arrays.asList("F"));graph1.put("E", Arrays.asList("H", "F"));graph1.put("F", Arrays.asList("G"));graph1.put("G", Collections.emptyList());graph1.put("H", Collections.emptyList());
?System.out.println("拓撲排序結果: " + topSortKahn(graph1));
?// 帶有環的圖Map<String, List<String>> graph2 = new HashMap<>();graph2.put("A", Arrays.asList("C"));graph2.put("B", Arrays.asList("C", "D"));graph2.put("C", Arrays.asList("E"));graph2.put("D", Arrays.asList("F"));graph2.put("E", Arrays.asList("C")); // 構成了環graph2.put("F", Arrays.asList("G"));graph2.put("G", Collections.emptyList());
?System.out.println("拓撲排序結果(存在環): " + topSortKahn(graph2));}
}

測試結果:

拓撲排序結果: [A, B, C, D, E, H, F, G]
拓撲排序結果(存在環): null

2 DFS算法實現

(1)算法思路
  1. 使用 DFS 遍歷圖。

  2. 遍歷過程中,將當前節點標記為“正在訪問”狀態。

  3. 遞歸訪問鄰接節點,如果發現鄰接節點已經在訪問中,說明存在環。

  4. 所有鄰接節點訪問完后,將當前節點加入結果棧,并標記為“訪問完成”。

  5. 最終將棧反轉,得到拓撲排序結果。

(2) 代碼實現(Java)
import java.util.*;
?
public class TopSort {
?// 枚舉節點訪問狀態enum State { UNVISITED, VISITING, VISITED }
?public static List<String> topSortDfs(Map<String, List<String>> graph) {Map<String, State> stateMap = new HashMap<>();List<String> result = new ArrayList<>();boolean[] hasCycle = new boolean[1]; // 記錄是否存在環
?// 初始化所有節點為 UNVISITEDfor (String node : graph.keySet()) {stateMap.put(node, State.UNVISITED);for (String neighbor : graph.get(node)) {stateMap.putIfAbsent(neighbor, State.UNVISITED);}}
?// 對每個節點進行 DFSfor (String node : stateMap.keySet()) {if (stateMap.get(node) == State.UNVISITED) {dfs(node, graph, stateMap, result, hasCycle);if (hasCycle[0]) {return null; // 有環}}}
?// 逆序輸出拓撲排序結果Collections.reverse(result);return result;}
?private static void dfs(String node, Map<String, List<String>> graph, Map<String, State> stateMap, List<String> result, boolean[] hasCycle) {stateMap.put(node, State.VISITING);
?for (String neighbor : graph.getOrDefault(node, Collections.emptyList())) {State neighborState = stateMap.get(neighbor);if (neighborState == State.UNVISITED) {dfs(neighbor, graph, stateMap, result, hasCycle);if (hasCycle[0]) return;} else if (neighborState == State.VISITING) {hasCycle[0] = true; // 檢測到環return;}}
?stateMap.put(node, State.VISITED);result.add(node); // 后序加入}
?
?// 測試用例public static void main(String[] args) {Map<String, List<String>> graph1 = new HashMap<>();graph1.put("A", Arrays.asList("C"));graph1.put("B", Arrays.asList("C", "D"));graph1.put("C", Arrays.asList("E"));graph1.put("D", Arrays.asList("F"));graph1.put("E", Arrays.asList("H", "F"));graph1.put("F", Arrays.asList("G"));graph1.put("G", Collections.emptyList());graph1.put("H", Collections.emptyList());
?System.out.println("拓撲排序結果: " + topSortDfs(graph1));
?// 帶有環的圖Map<String, List<String>> graph2 = new HashMap<>();graph2.put("A", Arrays.asList("C"));graph2.put("B", Arrays.asList("C", "D"));graph2.put("C", Arrays.asList("E"));graph2.put("D", Arrays.asList("F"));graph2.put("E", Arrays.asList("C")); // 構成了環graph2.put("F", Arrays.asList("G"));graph2.put("G", Collections.emptyList());
?System.out.println("拓撲排序結果(存在環): " + topSortDfs(graph2));}
}

測試結果:

拓撲排序結果: [B, D, A, C, E, F, G, H]
拓撲排序結果(存在環): null

三、實戰用例:具有優先級的任務調度系統

1 問題描述

我們有一個調度系統,其每個任務可能依賴其他任務,并且每個任務有一個優先級值(值越大優先級越高)。系統要求先執行優先級高的任務,同時也要保證所有依賴任務已經執行。

2 解決方案

我們結合拓撲排序 + 優先級排序解決:

  1. 將任務依賴建圖。

  2. 使用拓撲排序(Kahn 算法)。

  3. 使用優先級高的任務優先執行:將普通隊列替換成優先級隊列,先處理高優先級入度為 0 的任務。

3 Java 實現

import java.util.*;public class TaskSchedulerWithPriority {public static class Task {private int id;private int priority;private List<Task> edges = new ArrayList<>(); // 邊列表private int inDegree = 0; // 入度public Task(int id, int priority) {this.id = id;this.priority = priority;}public int getId() {return id;}public int getPriority() {return priority;}public void setPriority(int priority) {this.priority = priority;}public List<Task> getEdges() {return edges;}public int getInDegree() {return inDegree;}public void setInDegree(int inDegree) {this.inDegree = inDegree;}@Overridepublic String toString() {return "Task{id=" + id + ", priority=" + priority + "}";}}/*** 按優先級和依賴順序進行拓撲排序*/public static List<Task> scheduleTasks(List<Task> tasks) {// 大頂堆:優先處理優先級高的任務PriorityQueue<Task> queue = new PriorityQueue<>((a, b) -> b.getPriority() - a.getPriority());// 初始化:將所有入度為 0 的任務放入隊列for (Task task : tasks) {if (task.getInDegree() == 0) {queue.offer(task);}}List<Task> result = new ArrayList<>();while (!queue.isEmpty()) {Task current = queue.poll();result.add(current);// 遍歷后繼節點for (Task neighbor : current.getEdges()) {neighbor.setInDegree(neighbor.getInDegree()-1);if (neighbor.getInDegree() == 0) {queue.offer(neighbor);}}}// 檢查是否有環if (result.size() != tasks.size()) {throw new RuntimeException("存在循環依賴,無法完成調度");}return result;}public static void main(String[] args) {// 創建任務Task t0 = new Task(0, 10);Task t1 = new Task(1, 20);Task t2 = new Task(2, 10);Task t3 = new Task(3, 20);// 構建依賴關系 (鄰接表 + 入度)t0.getEdges().add(t2);t1.getEdges().add(t2);t2.getEdges().add(t3);t2.setInDegree(2); // 來自 t0 和 t1t3.setInDegree(1); // 來自 t2List<Task> tasks = Arrays.asList(t0, t1, t2, t3);// 調度List<Task> schedule = scheduleTasks(tasks);System.out.print("調度任務執行順序: ");for (Task task : schedule) {System.out.print(task.id + " ");}}
}

測試結果

調度任務執行順序: [1, 0, 2, 3]

表示優先級高的任務 1 和 0 會先執行,然后是它們的依賴任務 2 和最終任務 3。

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

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

相關文章

GoBy 工具聯動 | GoBy AWVS 自動化漏掃工作流

GoBy 系統筆記導航 &#x1f680;&#xff1a;[網安工具] Web 漏洞掃描工具 —— GoBy 使用手冊 AWVS 系統筆記導航 &#x1f680;&#xff1a;[網安工具] Web 漏洞掃描工具 —— AWVS 使用手冊 0x01&#xff1a;GoBy AWVS —— 聯動掃描簡介 AWVS 是一款由 Acunetix 公司開…

《匯編語言:基于X86處理器》第13章 高級語言接口(1)

與C、c&#xff0c;Java等高級語言相比&#xff0c;匯編開發的效率偏低和維護成本偏高。大型的項目已經很少用匯編語言了&#xff0c;但并不是說匯編語言就完全沒有用處了&#xff0c;在某些特定的領域&#xff0c;匯編語言還是很有用處的&#xff0c;比如配置硬件驅動器&#…

JVM基礎【Java】

JVM基礎 JVM&#xff1a;Java Virtual Machine(Java虛擬機&#xff09; 1.Java文件的執行流程 首先認識Java文件的運行規則對字節碼文件進行解釋成機器碼&#xff0c;讓計算機執行內存管理 自動為對象、方法等分配內存自動垃圾回收機制&#xff0c;回收不再使用的對象 即時編譯…

ISL9V3040D3ST-F085C一款安森美 ON生產的汽車點火IGBT模塊,絕緣柵雙極型晶體管ISL9V3040D3ST汽車點火電路中的線圈驅動器

ISL9V3040D3ST-F085C 是一款 安森美 &#xff08;ON&#xff09;生產的汽車點火 IGBT模塊&#xff08;絕緣柵雙極型晶體管&#xff09;&#xff0c;主要用于汽車點火電路中的線圈驅動器&#xff0c;具有內部二極管電壓箝位功能&#xff0c;可減少外部組件需求。? 核心用途 該…

用Python實現Excel轉PDF并去除Spire.XLS水印

最近業務需要&#xff0c;成功用Python原生代碼實現了原本需要付費的Spire.XLS庫的Excel轉PDF功能&#xff0c;并徹底去除了轉換后PDF中的評估水印"Evaluation Warning: The document was created with Spire.XLS for Python"。該解決方案完全開源免費&#xff0c;不…

論文學習22:UNETR: Transformers for 3D Medical Image Segmentation

代碼來源 unetr 模塊作用 具有收縮和擴展路徑的全卷積神經網絡 (FCNN) 在大多數醫學圖像分割應用中表現出色&#xff0c;但卷積層的局部性限制了其學習長距離空間依賴性的能力。受 Transformer 在自然語言處理 (NLP) 領域近期在長距離序列學習方面取得的成功的啟發&#xff…

Jmeter使用第一節-認識面板(Mac版)

常用的基礎元件&#xff08;10個&#xff09;1、測試計劃&#xff1a;總體項目容器&#xff0c;其他元件需要建立在這個目錄下面2、線程組&#xff1a;可以設置線程數、循環次數等參數來模擬用戶行為。一個用戶可用于接口測試&#xff0c;多個用戶則可用于性能壓測。“線程數”…

微軟披露Exchange Server漏洞:攻擊者可靜默獲取混合部署環境云訪問權限

微軟近日發布安全公告&#xff0c;披露一個影響本地版Exchange Server的高危漏洞&#xff08;編號CVE-2025-53786&#xff0c;CVSS評分為8.0&#xff09;。該漏洞在特定條件下可能允許攻擊者提升權限&#xff0c;Outsider Security公司的Dirk-jan Mollema因報告此漏洞獲得致謝。…

大模型中的反向傳播是什么

反向傳播&#xff08;Backpropagation&#xff09;是大模型&#xff08;如GPT、BERT等&#xff09;訓練過程中的核心算法&#xff0c;用于高效計算損失函數對神經網絡中所有參數的梯度。這些梯度隨后被用于優化器&#xff08;如Adam&#xff09;更新參數&#xff0c;使模型逐漸…

數集相等定義凸顯解析幾何幾百年重大錯誤:將無窮多各異點集誤為同一集

數集相等定義凸顯解析幾何幾百年重大錯誤&#xff1a;將無窮多各異點集誤為同一集 黃小寧 本文據中學生就應熟悉的數集相等概念推翻了直線公理和平面公理表明“舉世公認”不能是檢驗真理的唯一標準。“真理往往在少數人手里”。 請看圖片舉世公認&#xff1a;因數學是嚴密精確的…

container_of函數使用

用于根據結構體成員的地址反推整個結構體地址的宏定義。其核心作用是通過成員變量地址定位到其所屬的結構體實例。struct panel_tm145{struct drm_panel base;}static inline struct panel_tm145 * to_panel_tm145(struct drm_panel *panel){return container_of(panel, struct…

【MySQL基礎篇】:MySQL索引——提升數據庫查詢性能的關鍵

?感謝您閱讀本篇文章&#xff0c;文章內容是個人學習筆記的整理&#xff0c;如果哪里有誤的話還請您指正噢? ? 個人主頁&#xff1a;余輝zmh–CSDN博客 ? 文章所屬專欄&#xff1a;MySQL篇–CSDN博客 文章目錄索引一.MySQL與存儲二.索引的理解1.Page頁模式理解單個Page理解…

TD-IDF的一些應用

TF-IDF&#xff08;詞頻 - 逆文檔頻率&#xff09;作為經典的文本特征提取算法&#xff0c;在自然語言處理&#xff08;NLP&#xff09;領域應用廣泛。它能將文本轉化為可量化的數值特征&#xff0c;為后續的數據分析和建模提供基礎。本文結合實際場景&#xff0c;介紹如何用 P…

Redis 緩存問題詳解及解決方案

一、緩存擊穿 (Cache Breakdown) 原理&#xff1a; 某個熱點 Key 突然過期&#xff0c;同時大量并發請求該 Key&#xff0c;導致請求直接穿透緩存擊穿到數據庫。 解決方案&#xff1a; 互斥鎖 (Mutex Lock) 當緩存失效時&#xff0c;僅允許一個線程重建緩存&#xff0c;其他線程…

一周一個數據結構 第一周 --- 順序表(下)

文章目錄一、ArrayList的構造二、ArrayList常見操作三、ArrayList的遍歷四、ArrayList練習1.【小練習】2.楊輝三角3.簡單的洗牌算法五、ArrayList小結在上一章節中&#xff0c;我們通過代碼示例以及畫圖的方式詳細了解了順序表&#xff0c;并模擬實現了它。那么&#xff0c;是不…

OpenCV的關于圖片的一些運用

一、讀取圖片通過cv2庫中的imread&#xff08;&#xff09;方法讀取圖片代碼&#xff1a;import cv2 a cv2.imread(1.png) cv2.imshow(tu,a) b cv2.waitKey(4000) # 圖片執行時間 cv2.destroyAllWindows() # 關閉所有端口 print("圖像形狀(shape):",a.shape) print…

【數據結構——并查集】

引入 并查集&#xff08;Disjoint Set Union&#xff0c;DSU&#xff09;是一種用于管理元素分組的數據結構。 合并&#xff08;Union&#xff09;&#xff1a;將兩個不相交的集合合并為一個集合。 查找&#xff08;Find&#xff09;&#xff1a;確定某個元素屬于哪個集合&…

在 Vue 中使用 ReconnectingWebSocket實現即時通訊聊天客服功能

在 Vue 中使用 ReconnectingWebSocketReconnectingWebSocket 是一個自動重連的 WebSocket 實現&#xff0c;非常適合在 Vue 項目中使用。下面是如何在 Vue 中集成和使用它的方法&#xff1a;搜索 "程序員老狼"安裝 ReconnectingWebSocket首先&#xff0c;你需要安裝…

智能體革命:網絡安全人的角色重塑與突圍指南

AI賦能千行百業的趨勢不可逆轉&#xff0c;當AI學會滲透測試&#xff0c;安全工程師的出路在哪里&#xff1f; 2025年8月7日&#xff0c;OpenAI正式發布GPT-5的消息刷屏科技圈。這個達到博士生水平的“統一”人工智能模型&#xff0c;將AI幻覺率降低60%&#xff0c;成本下降45%…

用于水T1值和脂肪分數量化的上半身自由呼吸磁共振指紋成像|文獻速遞-醫學影像算法文獻分享

Title題目Upper-body free-breathing Magnetic Resonance Fingerprinting applied tothe quantification of water T1 and fat fraction用于水T1值和脂肪分數量化的上半身自由呼吸磁共振指紋成像 01文獻速遞介紹磁共振指紋成像&#xff08;MRF&#xff09;是十年前推出的一種高…