引入
有向無環圖(DAG)
如果一個有向圖不存在環,也就是任意結點都無法通過一些有向邊回到自身,那么稱這個有向圖為有向無環圖,
AOV網路
在有向圖中,用頂點表示活動,用有向邊$ < V_i, V_j > $表示活動 $i$ 是活動 $j$ 的必須條件,這種有向圖稱為用頂點表示活動的網路(Active on vertices),簡稱AOV網路,
在AOV網路中,如果活動$V_i$必須在$V_j$之前進行,則存在有向邊$<V_i, V_j>$,并稱$V_i$是$V_j$的直接前驅,$V_j$是$V_i$的直接后繼,這種前驅與后繼的關系具有傳遞性和反自反性,這要求AOV網路中不能出現回路,即有向環,因此,對于給定的AOV網路,必須先判斷它是否存在有向環,
拓撲排序
檢測有向環可以通過對AOV網路進行拓撲排序,該程序將各個頂點排列成一個線性有序的序列,使得AOV網路中所有的前驅和后繼關系都能得到滿足, 如果拓撲排序能夠將AOV網路的所有頂點都排入一個拓撲有序的序列中,則說明該AOV網路中沒有有向環,否則AOV網路中必然存在有向環,AOV網路的頂點的拓撲有序序列不唯一,可以將拓撲排序看做是將圖中的所有節點在一條水平線上的展開,圖的所有邊都從左指向右,
用計算機專業的幾門課程的學習次序來描述拓撲關系 ,顯然對于一門課來說,必須先學習它的先導課程才能更好地學習這門課程,比如學資料結構必須先學習C語言和離散數學,而且先導課程中不能有環,否則沒有盡頭了

而且還可以發現,如果兩門課程之間沒有直接或間接的先導關系,那么這兩門課的學習先后是任意的(比如“C語言”和“離散數學”的學習順序就是任意的),于是上述課程就可以排成一個水平展開的先后順序,如下圖

拓撲排序的結果不唯一,比如“C語言”和“離散數學”就可以換下順序,又或者把“計算機導論”向前放在任何一個位置都可以,總結一下就是,如果某一門課沒有先導課程或是所有的先導課程都已經學習完畢,那么這門課就可以學習了,如果同時有多門這樣的課,它們的學習順序任意,
演算法描述
對于一個有向無環圖
(1)統計所有節點的入度,對于入度為0的節點就可以分離出來,然后把這個節點指向所有的節點的入度$-1$,
(2)重復(1),直到所有的節點都被分離出來,拓撲排序結束,
(3)如果最后不存在入度為0的節點,那就說明有環,無解,
解釋一下,假設A為一個入度為0的結點,就表示A結點沒有前驅結點,可以直接做,把A完成后,對于A的所有后繼結點來說,前驅結點就完成了一個,入度進行$-1$,

時間復雜度
如果AOV網路有n個頂點,e條邊,在拓撲排序的程序中,搜索入度為零的頂點所需的時間是O(n),在正常情況下,每個頂點進一次堆疊,出一次堆疊,所需時間O(n),每個頂點入度減1的運算共執行了e次,所以總的時間復雜為O(n+e),
因為拓撲排序的結果不唯一,所以題目一般會要求按某種順序輸出,就需要使用優先級佇列,這里采取了最小字典序輸出,
vector<int>head[505], ans; int n, m, in[505];//入度序列 void topologicalSorting() { cin >> n >> m; for (int i = 0; i < m; i++) { int c1, c2; scanf("%d%d", &c1, &c2); head[c1].push_back(c2); in[c2]++; } priority_queue<int, vector<int>, greater<int>>q; for (int i = 1; i <= n; i++) { if (!in[i]) { q.push(i); } } while (!q.empty() && ans.size() < n) { int v = q.top(); q.pop(); ans.push_back(v); for (int i = 0; i < head[v].size(); i++) { in[head[v][i]]--; if (!in[head[v][i]]) q.push(head[v][i]); } } if (ans.size() == n) { //找到拓撲排序序列 } else { //圖中有環 } }
練習
模板題
題目鏈接:http://acm.hdu.edu.cn/showproblem.php?pid=1285
#include <iostream> #include <algorithm> #include <queue> #include <stdio.h> #include <vector> using namespace std; vector<int>head[505]; int in[505]; int main() { int n, m; while (cin >> n >> m) { priority_queue<int, vector<int>, greater<int>>q; vector<int>ans; for (int i = 0; i < m; i++) { int c1, c2; scanf("%d%d", &c1, &c2); head[c1].push_back(c2); in[c2]++; } for (int i = 1; i <= n; i++) { if (!in[i]) { q.push(i); } } while (!q.empty()) { int temp = q.top(); q.pop(); ans.push_back(temp); for (int i = 0; i < head[temp].size(); i++) { in[head[temp][i]]--; if (!in[head[temp][i]]) q.push(head[temp][i]); } } if (ans.size() == n) { for (int i = 0; i < n; i++) { head[i + 1].clear(); cout << ans[i]; if (i != n - 1)cout << ' '; } cout << endl; } q.emplace(); ans.clear(); } return 0; }View Code
反向拓撲排序
題目鏈接:http://acm.hdu.edu.cn/showproblem.php?pid=4857
題意:$n$個結點,給定$m$個拓撲關系$(a,b)$表示$a$必須排在$b$前面,在滿足$m$個拓撲關系關系的同時,使得小的結點盡可能的排在前面,
乍一看好像直接拓撲排序就行,但是看一個例子:
$6 \rightarrow 3\rightarrow 1\\5 \rightarrow 4 \rightarrow 2$
直接拓撲排序的結果是:$5\ 4\ 2\ 6\ 3\ 1$ ,是錯誤的,因為我們可以把1號安排到更前面的位置 即:$6\ 3\ 1\ 5\ 4\ 2$(正確答案),所以直接拓撲排序是不行的,為什么會出現這樣的狀況,對于多個拓撲關系,我們本來的策略是優先洗掉首結點較小的拓撲序列(比如5號結點比6號結點小,我們先洗掉了5號結點),但我們希望的是優先洗掉尾結點較小的拓撲序列(比如1號結點比2號結點小,應當先洗掉1號結點),問題找到了,我們可以嘗試一下逆向思維,即我們先考慮哪些點應該靠后釋放,就是把原來的拓撲關系反過來
$1 \rightarrow 3 \rightarrow 6\\2 \rightarrow 4 \rightarrow 5$
這樣我們按照優先洗掉首結點較大的拓撲序列得到的結果是$2\ 4\ 5\ 1\ 3\ 6$,好像還是不太對,把它逆序輸出就對啦!
#include <iostream> #include <algorithm> #include <queue> #include <stdio.h> #include <vector> using namespace std; vector<int>head[30005]; int in[30005]; int main() { int T; cin >> T; while (T--) { int n, m; cin >> n >> m; priority_queue<int>q; vector<int>ans; for (int i = 0; i < m; i++) { int c1, c2; scanf("%d%d", &c1, &c2); /*head[c1].push_back(c2); in[c2]++;*/ head[c2].push_back(c1); in[c1]++; } for (int i = 1; i <= n; i++) { if (!in[i]) { q.push(i); } } while (!q.empty()) { int temp = q.top(); q.pop(); ans.push_back(temp); for (int i = 0; i < head[temp].size(); i++) { in[head[temp][i]]--; if (!in[head[temp][i]]) q.push(head[temp][i]); } } if (ans.size() == n) { for (int i = n - 1; i >= 0 ; i--) { head[i + 1].clear(); cout << ans[i]; if (i != 0)cout << ' '; } cout << endl; } q.emplace(); ans.clear(); } return 0; }
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/137167.html
標籤:其他
上一篇:資料結構筆記1(c++)_指標
