拓撲排序C++

拓撲排序C++

幾個基本概念的介紹

入度和出度

圖中的度:所謂頂點的度(degree),就是指和該頂點相關聯的邊數。在有向圖中,度又分為入度和出度。

入度 (in-degree) :以某頂點為弧頭,終止于該頂點的邊的數目稱為該頂點的入度

出度 (out-degree) :以某頂點為弧尾,起始于該頂點的弧的數目稱為該頂點的出度

鄰接表

鄰接表,存儲方法跟樹的孩子鏈表示法相類似,是一種順序分配和鏈式分配相結合的存儲結構。如這個表頭結點所對應的頂點存在相鄰頂點,則把相鄰頂點依次存放于表頭結點所指向的單向鏈表中。

  • 在有向圖中,描述每個點向別的節點連的邊(點a->點b這種情況)。

  • 在無向圖中,描述每個點所有的邊(點a-點b這種情況)

LeetCode習題

207. 課程表

解題思路

本題可約化為: 課程安排圖是否是有向無環圖(DAG)。即課程間規定了前置條件,但不能構成任何環路,否則課程前置條件將不成立。
思路是通過 拓撲排序 判斷此課程安排圖是否是有向無環圖(DAG) 。

拓撲排序原理: 對 DAG 的頂點進行排序,使得對每一條有向邊 (u,v)(u,v)(u,v),均有 uuu(在排序記錄中)比 vvv 先出現。亦可理解為對某點 vvv 而言,只有當 vvv 的所有源點均出現了,vvv 才能出現。

通過課程前置條件列表 prerequisites 可以得到課程安排圖的鄰接表 adjacency,以降低算法時間復雜度,以下兩種方法都會用到鄰接表。

方法一:入度表(BFS)

本方法中幾個數據結構的含義:

  • vector<vector<int>> prerequisites 題目給出參數,其中每個元素 p 是一個依賴關系 p[0] 依賴于 p[1] ,在有向圖中,應該是 p[1]->p[0]

  • vector<int> degress 記錄所有節點的入度

  • vector<vector<int>> adjacents 鄰接表,長度為總課程數,下標 iii 的元素存放所有依賴節點 iii 的節點

  • queue<int> zeros 存放所有目前入度為 0 的頂點

算法流程:

  1. 統計課程安排圖中每個節點的入度,生成 入度表 indegrees
  2. 借助一個隊列 queue,將所有入度為 0 (沒有任何依賴)的節點入隊。
  3. queue 非空時,依次將隊首節點出隊,在課程安排圖中刪除此節點 pre
    • 并不是真正從鄰接表中刪除此節點 pre,而是將此節點鄰接表對應所有鄰接節點 cur,即所有以來該節點的節點的入度 ?1,即 indegrees[cur] -= 1
    • 當入度 ?1 后鄰接節點 cur 的入度為 0,說明 cur 所有的前驅節點(依賴節點)已經被 “刪除”,此時將 cur 入隊。
  4. 在每次 pre 出隊時,執行 numCourses--
    • 若整個課程安排圖是有向無環圖(即可以安排),則所有節點一定都入隊并出隊過,即完成拓撲排序。換個角度說,若課程安排圖中存在環,一定有節點的入度始終不為 0。
    • 因此,拓撲排序出隊次數等于課程個數,返回 numCourses == 0 判斷課程是否可以成功安排。
class Solution {
public:bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {vector<int> degrees(numCourses, 0);		// 記錄所有頂點的入度,未初始化的為0vector<vector<int>> adjacents(numCourses);	// 鄰接表queue<int> zero;	// 零入度的頂點int num = numCourses;for (int i=0; i<prerequisites.size(); ++i) {degrees[prerequisites[i][0]]++;		// 入頂點adjacents[prerequisites[i][1]].push_back(prerequisites[i][0]);		// 出頂點}for (int i=0; i<numCourses; ++i) {if (degrees[i] == 0) {zero.push(i);		// 入度為0的先入隊列--num;}}while (!zero.empty()) {int temp = zero.front();zero.pop();for (int j=0; j<adjacents[temp].size(); ++j) {if (--degrees[adjacents[temp][j]] == 0) {zero.push(adjacents[temp][j]);--num;}}}if (num == 0) return true;else return false;}
};

方法二:DFS

原理是通過 DFS 判斷圖中是否有環。

算法流程:

  1. 借助一個標志列表 flag,用于判斷每個節點 i (課程)的狀態:
    1. 未被 DFS 訪問:i == 0
    2. 已被其他節點啟動的 DFS 訪問:i == -1
    3. 已被當前節點啟動的 DFS 訪問:i == 1
  2. numCourses 個節點依次執行 DFS,判斷每個節點起步 DFS 是否存在環,若存在環直接返回 False。DFS 流程:
    • 終止條件:
      1. flag[i] == -1,說明當前訪問節點已被其他節點啟動的 DFS 訪問,無需再重復搜索,直接返回 True。
      2. flag[i] == 1,說明在本輪 DFS 搜索中節點 i 被第 2 次訪問,即 課程安排圖有環 ,直接返回 False。
    • 將當前訪問節點 i 對應 flag[i] 置 1,即標記其被本輪 DFS 訪問過;
    • 遞歸訪問當前節點 i 的所有鄰接節點 j,當發現環直接返回 False;
    • 當前節點所有鄰接節點已被遍歷,并沒有發現環,則將當前節點 flag 置為 ?1 并返回 True。
  3. 若整個圖 DFS 結束并未發現環,返回 True。
class Solution {
public:bool dfs(vector<vector<int>>& adjacents, vector<int>& flags, int curr) {if (flags[curr] == 1) return false;else if (flags[curr] == -1) return true;flags[curr] = 1;for (int i=0; i<adjacents[curr].size(); ++i) {if (!dfs(adjacents, flags, adjacents[curr][i])) return false;}flags[curr] = -1;return true;}bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {vector<vector<int>> adjacents(numCourses);vector<int> flags(numCourses, 0);for (vector<int> p: prerequisites) {adjacents[p[1]].push_back(p[0]);}for (int i=0; i<numCourses; ++i) {if (!dfs(adjacents, flags, i))  return false;}return true;}
};

210. 課程表 II

與上題思路一致

BFS

class Solution {
private:// 存儲有向圖vector<vector<int>> edges;// 存儲每個節點的入度vector<int> indeg;// 存儲答案vector<int> result;public:vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {edges.resize(numCourses);indeg.resize(numCourses);for (const auto& info: prerequisites) {edges[info[1]].push_back(info[0]);++indeg[info[0]];}queue<int> q;// 將所有入度為 0 的節點放入隊列中for (int i = 0; i < numCourses; ++i) {if (indeg[i] == 0) {q.push(i);}}while (!q.empty()) {// 從隊首取出一個節點int u = q.front();q.pop();// 放入答案中result.push_back(u);for (int v: edges[u]) {--indeg[v];// 如果相鄰節點 v 的入度為 0,就可以選 v 對應的課程了if (indeg[v] == 0) {q.push(v);}}}if (result.size() != numCourses) {return {};}return result;}
};

DFS

class Solution {
private:vector<vector<int>> edges;vector<int> visited;bool valid = true;stack<int> S;void dfs(int u) {visited[u] = 1;for (int v : edges[u]) {if ( visited[v] == 1) {valid = false;return;}else if (visited[v] == 0) {dfs(v);if (!valid) return;}}visited[u] = 2;S.push(u);}public:vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {edges.resize(numCourses);visited.resize(numCourses);for (const auto& v : prerequisites) {edges[v[1]].push_back(v[0]);}for (int i=0; i<numCourses && valid; i++) {if (!visited[i]) dfs(i);}if (!valid) return {};else {vector<int> res;while (!S.empty()) {res.push_back(S.top());S.pop();}return res;}

解題思路參考:https://leetcode-cn.com/problems/course-schedule/solution/course-schedule-tuo-bu-pai-xu-bfsdfsliang-chong-fa/

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

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

相關文章

C++面試常考題——編譯內存相關

C面試常考題——編譯內存相關 轉自&#xff1a;https://leetcode-cn.com/leetbook/read/cpp-interview-highlights/e4ns5g/ C程序編譯過程 編譯過程分為四個過程&#xff1a;編譯&#xff08;編譯預處理、編譯、優化&#xff09;&#xff0c;匯編&#xff0c;鏈接。 編譯預處…

C++遍歷刪除元素

C遍歷刪除元素 轉自&#xff1a;http://zencoder.info/2019/10/11/erase-element-from-container/ 今天看到一個patch fix從std::map中遍歷刪除元素導致crash問題&#xff0c;突然意識到自己對如何正確地從map等C容器中刪除元素也沒有很牢固清醒的認知。重新梳理了下這塊的正…

關鍵字庫函數

關鍵字庫函數 轉自&#xff1a;https://leetcode-cn.com/leetbook/read/cpp-interview-highlights/ej3mx1/ sizeof和strlen的區別 strlen 是頭文件<cstring> 中的函數&#xff0c;sizeof 是 C 中的運算符。 strlen 測量的是字符串的實際長度&#xff08;其源代碼如下&…

memcpy和memmove的區別以及內存重疊問題

memcpy和memmove的區別以及內存重疊問題 轉自&#xff1a;https://www.codecomeon.com/posts/89/ 區別 memcpy() 和 memmove() 都是C語言中的庫函數&#xff0c;在頭文件 string.h 中&#xff0c;作用是拷貝一定長度的內存的內容&#xff0c;原型分別如下&#xff1a; void…

從頭搭建一個深度學習框架

從頭搭建一個深度學習框架 轉自&#xff1a;Build a Deep Learning Framework From Scratch 代碼&#xff1a;https://github.com/borgwang/tinynn 當前深度學習框架越來越成熟&#xff0c;對于使用者而言封裝程度越來越高&#xff0c;好處就是現在可以非常快速地將這些框架作為…

關于python import的sys.path路徑問題

關于python import的sys.path路徑問題 sys.path 先說一下 sys.path 這個變量&#xff0c;該變量需要導入 sys 官方庫方可使用&#xff0c;它是一個列表&#xff0c;是當前 python 文件 import 庫時會逐個搜索列表中的路徑。 初始化 sys.path 從這些位置初始化&#xff1a; …

python pdb調試基本命令整理

python pdb調試基本命令整理 使用簡介 啟動調試 侵入式 在 py 文件內部設置&#xff1a; import pdb; pdb.set_trace()程序會在運行到這一行時停下來&#xff0c;進入 pdb 交互。 非侵入式 在運行 py 腳本時&#xff1a; python -m pdb main.py程序會在一啟動時就進入 pdb 交…

Docker概念理解

Docker概念理解 本文非Docker命令大全&#xff0c;而是對Docker的概念、原理等作說明&#xff0c;適合有一定實操經驗后來加深理解。 轉自&#xff1a;docker從入門到實踐 Docker簡介 本章將帶領你進入 Docker 的世界。 什么是 Docker&#xff1f; 用它會帶來什么樣的好處&a…

Dockerfile詳解

Dockerfile詳解 轉自&#xff1a;https://yeasy.gitbook.io/docker_practice/ 使用Dockerfile定制鏡像 從剛才的 docker commit 的學習中&#xff0c;我們可以了解到&#xff0c;鏡像的定制實際上就是定制每一層所添加的配置、文件。如果我們可以把每一層修改、安裝、構建、操…

Dockerfile最佳實踐

Dockerfile最佳實踐 本文是原作者對 Docker 官方文檔中 Best practices for writing Dockerfiles 的理解與翻譯。 轉自&#xff1a;附錄四&#xff1a;Dockerfile 最佳實踐 一般性指南和建議 容器應該是短暫的 通過 Dockerfile 構建的鏡像所啟動的容器應該盡可能短暫&#xf…

Linux內存背后的那些神秘往事

Linux內存背后的那些神秘往事 作者&#xff1a;大白斯基&#xff08;公眾號&#xff1a;后端研究所&#xff09; 轉自&#xff1a;https://mp.weixin.qq.com/s/l_YdpyHht5Ayvrc7LFZNIA 前言 大家好&#xff0c;我的朋友們&#xff01; CPU、IO、磁盤、內存可以說是影響計算機…

mmdeploy快速上手

mmdeploy快速上手 若要將使用 openmmlab 的框架&#xff08;如mmdet、mmcls&#xff09;等訓練的模型進行快速部署&#xff0c;同樣來自 openmmlab 的 mmdeploy 無疑是最合適的選擇&#xff0c;本文將簡單地完成一個 Faster RCNN 模型的部署。 配置 本文基于如下軟硬件配置&…

精簡CUDA教程——CUDA Driver API

精簡CUDA教程——CUDA Driver API tensorRT從零起步邁向高性能工業級部署&#xff08;就業導向&#xff09; 課程筆記&#xff0c;講師講的不錯&#xff0c;可以去看原視頻支持下。 Driver API概述 CUDA 的多級 API CUDA 的 API 有多級&#xff08;下圖&#xff09;&#xff…

CUDA編程入門極簡教程

CUDA編程入門極簡教程 轉自&#xff1a;CUDA編程入門極簡教程 作者&#xff1a;小小將 前言 2006年&#xff0c;NVIDIA公司發布了CUDA&#xff0c;CUDA是建立在NVIDIA的CPUs上的一個通用并行計算平臺和編程模型&#xff0c;基于CUDA編程可以利用GPUs的并行計算引擎來更加高效地…

精簡CUDA教程——CUDA Runtime API

精簡CUDA教程——CUDA Runtime API tensorRT從零起步邁向高性能工業級部署&#xff08;就業導向&#xff09; 課程筆記&#xff0c;講師講的不錯&#xff0c;可以去看原視頻支持下。 Runtime API 概述 環境 圖中可以看到&#xff0c;Runtime API 是基于 Driver API 之上開發的…

Python并發——concurrent.futures梳理

Python并發——concurrent.futures梳理 參考官方文檔&#xff1a; concurrent.futures — 啟動并行任務 Executor對象 class concurrent.funtures.Executor該抽象類是 ThreadPoolExecutor 和 ProcessPoolExecutor 的父類&#xff0c;提供異步執行調用方法。要通過它的子類調用…

TensorRT ONNX 基礎

TensorRT ONNX 基礎 tensorRT從零起步邁向高性能工業級部署&#xff08;就業導向&#xff09; 課程筆記&#xff0c;講師講的不錯&#xff0c;可以去看原視頻支持下。 概述 TensorRT 的核心在于對模型算子的優化&#xff08;合并算子、利用當前 GPU 特性選擇特定的核函數等多種…

回文子串、回文子序列相關題目

回文子串、回文子序列相關題目 回文子串是要連續的&#xff0c;回文子序列可不是連續的。 516. 最長回文子序列 dp數組含義&#xff1a;dp[i][j]dp[i][j]dp[i][j] 表示子序列 s[i,j]s[i,j]s[i,j] 中的最長回文子序列的長度。 dp數組初始化&#xff1a;子序列長度為 1 時&am…

mmdetection tools工具梳理

mmdetection tools工具梳理 mmdetection 是一個非常好用的開源目標檢測框架&#xff0c;我們可以用它方便地訓練自己的目標檢測模型&#xff0c;mmdetection 項目倉庫提供許多實用的工具來實現幫助我們進行各種測試。本篇將梳理以下 mmdetection 項目倉庫 tools 目錄下的各種實…

TensorRT ONNX 基礎(續)

TensorRT ONNX 基礎&#xff08;續&#xff09; PyTorch正確導出ONNX 幾條推薦的原則&#xff0c;可以減少潛在的錯誤&#xff1a; 對于任何使用到 shape、size 返回值的參數時&#xff0c;例如 tensor.view(tensor.size(0), -1) 這類操作&#xff0c;避免直接使用 tensor.s…