图Graph--拓扑排序(Topological Sorting)
生活随笔
收集整理的這篇文章主要介紹了
图Graph--拓扑排序(Topological Sorting)
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
文章目錄
- 1. 拓撲排序
- 2. 算法實現
- 2.1 Kahn算法
- 2.2 DFS算法
- 2.3 時間復雜度
- 3. 應用
- 4. 類似題目練習
一個項目往往會包含很多代碼源文件。編譯器在編譯整個項目時,需按照依賴關系,依次編譯每個源文件。比如,A.cpp依賴B.cpp,那在編譯時,編譯器需要先編譯B.cpp,才能編譯A.cpp。
編譯器通過分析源文件或者編譯配置文件(比如Makefile文件),來獲取這種局部的依賴關系。那編譯器又該如何通過源文件兩兩之間的局部依賴關系,確定一個全局的編譯順序呢?
1. 拓撲排序
- 可以把源文件與源文件之間的依賴關系,抽象成一個有向圖。每個源文件對應圖中的一個頂點,源文件之間的依賴關系就是頂點之間的邊。
- 如果a先于b執行,也就是說b依賴于a,那么就在頂點a和頂點b之間,構建一條從a指向b的邊。而且,這個圖不僅要是有向圖,還要是一個有向無環圖,也就是不能存在像a->b->c->a這樣的循環依賴關系。
數據結構如下:
#include <list> using namespace std; class Graph {int v;//頂點個數list<int> *adj;//鄰接表 public:Graph(int vn){v = vn;adj = new list<int> [v];}~Graph(){delete [] adj;}void addEdge(int s, int t)//s先于t,邊s->t{adj[s].push_back(t);} };2. 算法實現
2.1 Kahn算法
- Kahn 算法是貪心思想
- 如果 s 需要先于 t 執行,就添加一條 s 指向 t 的邊。如果某個頂點入度為0,也就表示,沒有任何頂點必須先于這個頂點執行,那么這個頂點就可以執行了。
- 先從圖中,找出一個入度為0的頂點,將其輸出,并刪除這個頂點(也就是把這個頂點可達的頂點的入度都減1)。我們循環執行上面的過程,直到所有的頂點都被輸出。最后輸出的序列,就是滿足局部依賴關系的拓撲排序。
2.2 DFS算法
- 構造逆鄰接表。鄰接表中,邊 s->t 表示 s 先于 t 執行,也就是 t 要依賴 s。在逆鄰接表中,邊 s->t 表示 s 依賴于 t,s 后于 t 執行。
- 遞歸處理每個頂點。對頂點 i ,先輸出它可達的所有頂點,也就是,先把它依賴的所有的頂點輸出了,然后再輸出自己。
在上面程序代碼中添加
list<G_Node*> *reverseadj; //逆鄰接表 reverseadj = new list<G_Node*> [v]; void addEdge(char s, char t)//s先于t,邊s->t {int i = findIdx(s), j = findIdx(t);if(i != -1 && j != -1){adj[i].push_back(&pGNode[j]);//s->t,鄰接表pGNode[j].indegree++;reverseadj[j].push_back(&pGNode[i]);//逆鄰接表}} void topoSortByDFS() {cout << "topoSortByDFS:" << endl;bool *visited = new bool [v];memset(visited,0,v*sizeof(bool));for(int i = 0; i < v; ++i) //深度優先遍歷{if(visited[i] == false){visited[i] = true;dfs(i, reverseadj, visited);}}delete [] visited; } void dfs(int i, list<G_Node*> *reverseadj, bool *visited) {int idx;for(auto it = reverseadj[i].begin(); it != reverseadj[i].end(); ++it){idx = findIdx((*it)->info);if(visited[idx] == true)continue;visited[idx] = true;dfs(idx,reverseadj,visited);}cout << pGNode[i].info << "->"; }2.3 時間復雜度
- Kahn代碼中,每個頂點被訪問了一次,每個邊也都被訪問了一次,所以,Kahn算法的時間復雜度就是O(V+E)(V表示頂點個數,E表示邊的個數)。
- DFS算法中,每個頂點被訪問兩次,每條邊都被訪問一次,所以時間復雜度也是O(V+E)。
- 注意,這里的圖可能不是連通的,有可能是有好幾個不連通的子圖構成,所以,E并不一定大于V,V E的大小關系不定。所以,在表示時間復雜度的時候,V、E都要考慮在內。
3. 應用
- 拓撲排序應用非常廣泛。凡是需要通過局部順序來推導全局順序的,一般都能用拓撲排序來解決。
- 拓撲排序還能檢測圖中環的存在。對于Kahn算法來說,如果最后輸出出來的頂點個數,少于圖中頂點個數,圖中還有入度不是0的頂點,那就說明,圖中存在環。
- 關于圖中環的檢測,遞歸那節講過一個例子,在查找最終推薦人的時候,可能會因為臟數據,造成存在循環推薦,比如,用戶A推薦了用戶B,用戶B推薦了用戶C,用戶C又推薦了用戶A。如何避免這種臟數據導致的無限遞歸?
這就是環的檢測問題。因為我們每次都只是查找一個用戶的最終推薦人,所以,我們并不需要動用復雜的拓撲排序算法,而只需要記錄已經訪問過的用戶ID,當用戶ID第二次被訪問的時候,就說明存在環。
4. 類似題目練習
LeetCode 207. 課程表(拓撲排序)
LeetCode 210. 課程表 II(拓撲排序)
LeetCode 269. 火星詞典(拓撲排序)
LeetCode 851. 喧鬧和富有(拓撲排序)
LeetCode 1136. 平行課程(拓撲排序)
LeetCode 1203. 項目管理(兩次拓撲排序)
LeetCode 5665. 從相鄰元素對還原數組(拓撲排序)
LeetCode 5699. 從第一個節點出發到最后一個節點的受限路徑數(迪杰斯特拉 + 拓撲排序)
總結
以上是生活随笔為你收集整理的图Graph--拓扑排序(Topological Sorting)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: post请求改成body_如何使用BOD
- 下一篇: ubuntu memcached php