三、【圖演算法】DFS應用-拓撲排序
阿新 • • 發佈:2018-12-08
深度優先搜尋(DFS)演算法是最重要的圖遍歷演算法,基於DFS框架,可以匯出大量的圖演算法,圖的拓撲排序即為其中一個很典型的例子。
拓撲排序:給定一個有向圖,如何在保證“每個頂點都不會通過邊,指向其在此序列中的前驅頂點”這一前提下,將所有頂點排成一個線性序列。
例如:
在編寫教材時,由於各個知識點之間具有一定的依賴關係,如何將這些知識點串聯為一份教學計劃,保證在整個授課程序中,每節課的基本知識點均在之前已講授呢?
這個例子就是個典型的拓撲排序例項。
特點:有向無環圖的拓撲排序一定存在。
策略:在DFS中首先轉換至VISITED狀態的頂點是出度為0的頂點m,此頂點m對之後的搜尋沒有任何作用,這時就相當於拓撲排序的最後一個頂點,將頂點m剔除,之後的出度為0的頂點即為頂點m的前驅,這樣將DFS遍歷結束,即可將這些前後剔除的頂點連線起來構成一個拓撲排序。注意,多個連通域會執行多次DFS,這些DFS各自剔除的頂點之前其實是沒任何關係的,所以可以任意排列。
實現:由於整個圖可能具有多個連通域,從單個頂點開始的TSort可能不能遍歷到圖中的所有頂點,所以TSort函式能夠遍歷從頂點s開始的單個連通域,而tSort函式則對所有頂點進行檢查,只要未曾被訪問過,就從該點開始一次新的TSort搜尋,這樣就能保證所有的連通域都能夠被遍歷到。
本文使用的圖資料結構參見之前部落格https://blog.csdn.net/qq_18108083/article/details/84870399
template<typename Tv, typename Te> bool graph<Tv, Te>::TSort(int v, int& clock, stack<Tv>* S) //(基於DFS)單個連通域的拓撲排序,從連通域的任一頂點開始即可,因為會有外層tSort函式排查尚未發現的頂點 { status(v) = DISCOVERED; dTime(v) = ++clock; for (int u = firstNbr(v); u > -1; u = nextNbr(v, u)) { switch (status(u)) { case UNDISCOVERED: //若此頂點尚未被發現 status(u) = DISCOVERED; //標記為已被發現 type(v, u) = TREE; //標記邊e(v,u)為遍歷樹的枝幹 parent(u) = v; if (!TSort(u, clock, S)) //繼續深入遍歷 return false; break; case DISCOVERED: //若此頂點已被發現但尚未完成遍歷,則為迴環 type(v, u) = BACKWARD; return false; //發現此連通域為有環圖,不能生成拓撲序列 default: //VISITED 發現已經遍歷完畢的頂點 type(v, u) = (dTime(v) < dTime(u)) ? FORWARD : CROSS; //根據頂點最開始遍歷的時間標籤dTime判斷是前向邊還是兩個分支的跨邊 break; } } //此頂點已經完成遍歷 status(v) = VISITED; S->push(vdata(v)); return true; } template<typename Tv, typename Te> stack<Tv>* graph<Tv, Te>::tSort(int s) { reset(); stack<Tv>* S=new stack<Tv>; int v = s; int clock = 0; do { if (status(v) == UNDISCOVERED) { if (!TSort(v, clock, S)) //如果發現是有環圖不能生成拓撲序列,則返回 { while (!(S->empty())) { S->pop(); } break; } } } while ((v=(++v)%n)!=s); return S; }
效率:若圖G=(V,E)中共有n個頂點和e條邊,則tSort僅需O(n+e)時間。