拓撲排序ios
英文名稱:Topological-sort算法
別稱:toposort or topsort數據結構
如下進入胡扯時間 正題:spa
排序???code
a:我有sort!blog
b:我還會桶排!排序
c:我我我!我還會基數排序和計數排序遞歸
哇塞!厲害!string
可是你會這些東西和我拓撲排序有什麼關係it
a??b??c???
拓撲排序是幹什麼的呢
對一個有向無環圖(Directed Acyclic Graph簡稱DAG)G進行拓撲排序,是將G中全部頂點排成一個線性序列,使得圖中任意一對頂點u和v,若邊(u,v)∈E(G),則u在線性序列中出如今v以前。一般,這樣的線性序列稱爲知足拓撲次序(Topological Order)的序列,簡稱拓撲序列。簡單的說,由某個集合上的一個偏序獲得該集合上的一個全序,這個操做稱之爲拓撲排序。
以上來自360百科
看明白了嗎,反正我是不想看
嗯!這纔是正題。
首先,咱們由一個小問題引入。
有這麼一羣人,小紅愛着小綠,她得親眼看着小綠吃完飯她纔會安心吃飯,
而這個時候,小黃也愛着小綠,他也要親眼看着小綠把飯吃完她纔會安心。
同時,小藍愛着小紅和小黃,她得親眼看着小紅和小黃吃完飯她才能夠吃飯,
而小紫是個基佬,他不愛小紅,不愛小黃,不愛小藍,也不愛小綠,正由於他是基佬因此他對小紅小黃毫無威脅性,
因而小紫能夠同小綠一塊兒吃飯,固然也不能夠不。
那麼最終,你們吃飯的順序是怎樣的呢。
形象一點,畫個圖
大佬們看到這個小問題:這個sb題!這不是分分鐘秒切的事情嗎!
像我這種小菜雞:誒??爆搜嗎?
爆搜??什麼zz作法。別說,還真有點意思。
不過咱們首先講的,是Kahn算法,一看這個算法就很高級對不對!
對什麼對,只是聽起來高級而已。
其算法主要流程以下:
1.從圖中找到一個入度爲零的點,並輸出
2.在圖中刪去和這個點相連的全部邊,再重複1的操做
3.一直重複1.2的操做一直到圖中再也不有入度不爲零的點爲止。
固然,若是圖中有環那是無解的。
那麼它的複雜度是多少呢?
你猜你猜你猜
證實:初始化入度爲0的集合須要遍歷整張圖,檢查每一個節點和每條邊,對該集合進行操做,又須要遍歷整張圖中的,每條邊,則複雜度爲O(E+V);
代碼:
#include<stack> #include<cstdio> #include<vector> #include<cstring> #include<iostream> #include<algorithm> using namespace std; typedef long long ll; const int maxn = 1000 + 10; const int INF = 1e9 + 7; int T, n, m, num[maxn]; vector<int> vis[maxn], v; stack<int> s; void topo() { for(int i = 1; i <= n; i++) if(num[i] == 0) s.push(i); while(!s.empty()) { int now = s.top(); v.push_back(now); s.pop(); for(int j = 0; j < vis[now].size(); j++) if((--num[vis[now][j]]) == 0) s.push(vis[now][j]); } if(v.size() != n) cout << "NO solution" << '\n'; else for(int i = 0; i < v.size(); i++) cout<<v[i]<<" "; } int main() { scanf("%d%d", &n, &m); for(int i = 0; i <= n; i++) vis[i].clear(); memset(num, 0, sizeof(num)); for(int i = 0, u, v; i < m; i++) scanf("%d%d", &u, &v), vis[u].push_back(v), num[v]++; topo(); return 0; }
如今咱們再來說基於DFS的算法
須要注意的是,將頂點添加到結果anst中的時機是在vis方法即將退出之時。
搜索嘛,實踐略簡單,可是理解上要下點功夫。
其關鍵在於爲何在vis方法的最後將該頂點添加到一個集合中,就能保證這個集合就是拓撲排序的結果?
由於添加頂點到集合中的時機是在dfs方法即將退出之時,而dfs方法自己是個遞歸方法,只要當前頂點還存在邊指向其它任何頂點,它就會遞歸調用dfs方法,而不會退出。所以,退出dfs方法,意味着當前頂點沒有指向其它頂點的邊了,即當前頂點是一條路徑上的最後一個頂點。
那麼問題來了,這個方法對嗎?
你猜你猜你猜
證實:
考慮任意的邊,當調用dfs(v)的時候,有三種狀況:
須要注意的是,以上第三種狀況在拓撲排序的場景下是不可能發生的,由於若是狀況3是合法的話,就表示存在一條由w到v的路徑。而如今咱們的前提條件是由v到w有一條邊,這就致使咱們的圖中存在環路,從而該圖就不是一個有向無環圖(DAG),而咱們已經知道,非有向無環圖是不能被拓撲排序的。
那麼考慮前兩種狀況,不管是狀況1仍是狀況2,w都會先於v被添加到結果列表中。因此邊v->w老是由結果集中後出現的頂點指向先出現的頂點。爲了讓結果更天然一些,可使用棧來做爲存儲最終結果的數據結構,從而可以保證邊v->w老是由結果集中先出現的頂點指向後出現的頂點。
時間複雜度:
證實:DFS遍歷一遍的時間爲O(E+V),而記錄結果的時間花費爲O(1),因此總時間複雜度爲O(E+V)
代碼:
#include<cstdio> #include<vector> #include<cstring> #include<iostream> #include<algorithm>
using namespace std; const int maxn = 1000 + 10; const int INF = 1e9 + 7; int n, m, dis[maxn], ans[maxn], t; vector<int> vis[maxn]; bool dfs(int u) { dis[u] = -1; for(int i = 0; i < vis[u].size(); i++) { int v = vis[u][i]; if(dis[v] < 0) return false; else if(!dis[v] && !dfs(v)) return false; } dis[u] = 1, ans[--t] = u; return true; } bool toposort() { t = n; memset(dis, 0, sizeof(dis)); for(int u = 1; u <= n; u++) if(!dis[u]) if(!dfs(u)) return false; return true; } int main() { scanf("%d%d", &n, &m); for(int i = 0; i <= n; i++) vis[i].clear(); for(int i = 0, u, v; i < m; i++) scanf("%d%d", &u, &v), vis[u].push_back(v); if(toposort()) for(int i = 0; i < n; i++) printf("%d ",ans[i]); else puts("NO solution"); return 0; }
一世安寧