關於拓撲排序

拓撲排序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)的時候,有三種狀況:

  • dfs(w)尚未被調用,即所要走的點還未走,此時會調用dfs(w),而後當dfs(w)返回以後,dfs(v)纔會返回
  • dfs(w)已經被調用並返回了,即w已經被mark
  • dfs(w)已經被調用可是在此時調用dfs(v)的時候還未返回

須要注意的是,以上第三種狀況在拓撲排序的場景下是不可能發生的,由於若是狀況3是合法的話,就表示存在一條由wv的路徑。而如今咱們的前提條件是由vw有一條邊,這就致使咱們的圖中存在環路,從而該圖就不是一個有向無環圖(DAG),而咱們已經知道,非有向無環圖是不能被拓撲排序的。

那麼考慮前兩種狀況,不管是狀況1仍是狀況2w都會先於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; }

一世安寧

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息