新手入門 拓撲排序

2021-06-23 02:37:58 字數 3424 閱讀 7972

( ⊙ o ⊙ )!

o(∩_∩)o哈!

上個月學的拓撲排序,曾經覺得它很高深.今天就寫篇簡單的入門.也當是給自己複習好了~~~

咱們不用那些陌生的名詞.咱們通俗點講.

介個演算法是解決甚麼問題的呢?實際中,我們經常會碰到到這樣一類問題:一堆元素,不一定每兩個間都有嚴格的先後關係.先前的冒泡,快速排序等等等等都是建立在

每兩個元素間都可以比較出個結果來的,舉例:3, 1, 4, 1, 5排序,我們排出1,1,3,4,5的順序,是因為他們中任意兩個間都有大小關係:1<3,3<4,1<5等等.但是有這麼乙個

問題,7個室友在速射打遊戲,午飯時間到了,他們不願去食堂,太浪費時間了.所以他們準備每天選乙個人去買翔回來.然後每週大家用1+2+3+4+5+6+7=28元速射經費獎勵那個跑腿的. 作為跑腿費.周一獎勵1遠,以此類推...大家每人每週都得跑一次,當然就想盡量排在後面,就可以多拿點跑腿費了嘛~~~這時1~7號都有一些要求了:1號覺得他之前跑腿比5號

多,所以他起碼要比5號排在後面,不然不公平! 2號覺得自己得排在1號後面, 1號覺得自己得排在3號和6號的前面....

那有沒有一種安排方案讓大家的要求都得到滿足呢?

這個問題其實正是拓撲排序所回答的.從這裡也可以知道:拓撲排序給出的排序不一定是唯一的.

拓撲排序的過程如下:

首先建圖,如果a必須在b前面,就連一條a->b的有向邊.同時記錄每個頂點的入度(即連入該點的邊的數量),出度(從該點連出的邊的數量).

建圖可以自由發揮.我這裡給出一種方案供選用:

定義vectorg[max_v]存圖,開始時清空,比如有n個頂點,那麼g[i]對應的就是第i個頂點的*連出*的邊所對應的頂點編號.比如如果有一條邊

1->7那麼我們加邊時就應該這麼寫:g[1].push_back(7).很簡單明瞭吧??如果邊有邊權(在其它圖論問題中),就g[1][7] = val;(val就是1->7的邊權)

入度?入度用乙個陣列int in[max_v], in[i]對應第i個頂點的入度.初始化為0,以後每加一條邊u->v,就把in[v]加一

出度用陣列int out[max_v]表示,初始化為0,每加一條邊u->v就把out[u]加一.

看個實際問題:

對這個問題我們怎麼建圖呢?我們用上面的方法試一試:

#include #include //memset

#include #include using namespace std;

const int max_v = 128; //最多100個頂點

vectorg[max_v]; //存圖用,是不是和上面一模一樣?

int in[max_v]; //入度

int out[max_v]; //出度

int t; //題目的測試組數

int n; //頂點數目

int m; //邊的數目

int main()

int u, v; //u->v

for (int i = 0; i < m; ++i)

}return 0;

}

然後每次從當前剩下的入度為0的點中隨便拿乙個,來更新它的後繼邊.

試想,問題的關鍵就是要"滿足大家的需求",那麼這入度為0意味著神馬?意味著沒有邊連向它,也就是剩下的人中沒有人需要在它前面了.那我們就"滿足它",

把它排到現在的新佇列的後面,就當是搞定它了,然後再去滿足剩下的那些人就可以了.比如4個點,有且僅乙個條件是1要在2和3和4的前面.模擬一下我們

建圖的過程,入度為0的點只有1,所以我們就滿足它,把它排在第乙個位置,然後和它關聯的2,3,4因為還沒排,又因為我們排序時元素都是加到原有序列末尾的,所以

加了1後,2,3,4和1就相當於沒有任何關係了,因為就算加進序列那也是更晚加進去的,一定是在1後面的.所以把2,3,4和1的這個連線"割斷",也就是把他們的入度減1.

減完後如果入度變為0了就加到我們的"候選名單"中,這樣下一步我們就可以從這些名單中選乙個繼續我們無恥的排序了.

實現起來奏是:

/* top sort */

vectordong; //這就是我們的候選名單了,裡面將會存進那些入度為0的點

vectorans; //這裡儲存我們排好序的名單,每次拿出乙個入度為0的點後都丟到這裡來

for (int i = 1; i <= n; ++i)

}/* 因為拓撲排序是"每次拿乙個入度0的點出來",所以為了把n個元素排好,就需要n次迴圈. */

for (int i = 1; i <= n; ++i)

}} /* 完成了 */

上面只是乙個很粗糙的框架.在這裡我想表達的是:其一,拓撲排序應該怎麼實現,其二作為超級大水比的我們應該怎麼去建圖,儲存中間資訊,把演算法用**表達出來.開始可能很艱難.

但是當你不斷地嘗試這麼做之後,總有一天你會發現,你已經突破這個瓶頸,你可以開始認認真真地學習演算法思想本身,而不是碰到乙個題就手足無措找題解模仿了.實現方法太多了,但是無論你怎麼寫,永遠不要忘記演算法的核心是它的思想,那才是靈魂!

上面的問題中,我們應該怎麼判斷是否排序成功了呢?拓撲排序要求每次拿出乙個入度0的點來更新其它點,要做n次,那麼如果n次還沒拿完就已經沒有入度為0的點可以拿了,我們就

說排序失敗.

那怎麼對付字典序最小呢?我們不是有乙個待拿取的"候選列表"嗎?只要我們每次從這個列表中不是隨便拿,而是:總是拿編號最小的入度為0的點.就ok了~~~

int t = n+1, index;

for (int j = 0; j < dong.size(); ++j)

都是很隨便的,怎麼寫都行.俺剛ac了一下,上個月還wa無數次的題...現在覺得好簡單...

拓撲排序部分我的實現:

queueans; //因為要求字典序最小,我們就用佇列,先進先出嘛~~~

//候選列表,但是用優先佇列儲存,每次拿到的就定是序號最小的點了

priority_queue, greater> q;

for (int i = 1; i <= n; ++i)

}bool ok = true;//標記是否成功,我們知道,這裡唯一不成功的可能就

//是:某次我們想拿入度為0的點拿不到了~~~

for (int i = 1; i <= n; ++i)

int t = q.top(); q.pop();

ans.push(t);

for (int j = 0; j < g[t].size(); ++j)

}}

這題資料不大,所以無所謂.但是如果資料大了,這裡的優先佇列priority_queue會非常有用,當然,為了字典序,同樣的可以用set來儲存.stl的set內部是紅黑樹,效能非常優秀! priority_queue是堆實現,也很棒.插入都是o(logn)的,然後我們每次都只取編號最小的元素,所以查詢是o(1)的,非常高效^_^

俺的郵箱[email protected]

ubuntu新手入門

ubuntu使用設定之介面篇 ubuntu系統裝好了,不過怎麼看上則呢麼醜醜的?好像mr徐的介面和我的不一樣呢,怎麼設定的呢?首先,讓我們來設定工具條。ubuntu預設2條工具條,讓我們來把他設定成和windows下一樣的吧。先讓我們在上面的工具條的空白的地方,選擇刪除面板。然後,讓我們在底下的面板...

XML新手入門

ibm的xml教程 xml是什麼?xml,或稱為可擴充套件標記語言 extensible markup language 是一種您可以用來建立自己的標記的標記語言。它由全球資訊網協會 w3c 建立,用來克服 html 即超文字標記語言 hypertext markup language 它是所有網頁...

SOA新手入門

什麼是soa 我們可能應該回答的第乙個問題也是最基本的問題。什麼是面向服務的體系結構 service oriented architecture,soa 這個問題的答案實際上涉及與開發相關的若干不同方面。soa 是一種 it 體系結構樣式,支援將您的業務作為鏈結服務或可重複業務任務進行整合,可在需要...