拓扑排序基础讲解
拓撲排序(TopSort)
2021年8月5日
文章目錄
- 拓撲排序(TopSort)
- 1、算法原理
- 1.1 一個問題
- 1.2 如何通過計算機輸出排序結(jié)果?
- 1.3 圖示
- 1.4 不穩(wěn)定性
- 1.5 無法排序?
- 1.6 時間復(fù)雜度
- 2、算法實現(xiàn)
- 2.1 題目
- 2.2 解法分析
- 2.3 完整代碼
- 幾道例題
- A:[POJ - 2367 ](https://vjudge.net/problem/POJ-2367/origin) Genealogical tree
- B:[HDU - 1285](https://vjudge.net/problem/HDU-1285/origin)[ 確定比賽名次](https://vjudge.net/problem/HDU-1285)
- C:[HDU - 2094](https://vjudge.net/problem/HDU-2094/origin)[產(chǎn)生冠軍](https://vjudge.net/problem/HDU-2094)
1、算法原理
1.1 一個問題
在日常生活中,我們的某些行為需要有先后順序。比如,你需要先穿襪子再穿鞋,先拿起手表再戴手表,順序無法改變。
同理在一個工程中,每一步的工作都需要先后順序,某些工作的完成是另一些工作的前置條件。我們稱這每一步的工作為事件。
那么,若已知每個事件的前置事件,怎么確定其完成的先后順序?
我們可以構(gòu)建一個網(wǎng)絡(luò),將每個事件的前置事件放置在這個事件之前,并用有向線段連接,由此可以直觀地看出我們需要完成事件的先后順序。
此時構(gòu)建的網(wǎng)絡(luò),通常被稱為頂點活動網(wǎng)(Activity On Vertex network),簡稱AOV網(wǎng)。
1.2 如何通過計算機輸出排序結(jié)果?
拓撲排序就是針對這一類問題進行的排序算法。
建立上述AOV網(wǎng)之后,我們可對每個事件所構(gòu)成的結(jié)點進行入度的記錄。
首先,將所有入度為0的結(jié)點加入隊列(這些事件不需要前置事件),對其后續(xù)結(jié)點進行遍歷。遍歷到一個結(jié)點后,此結(jié)點的入度就減1,當(dāng)入度減為0,加入隊列(前置事件均已完成),對其后續(xù)結(jié)點進行遍歷。
當(dāng)所有結(jié)點遍歷過后,就可以知道其先后順序了。
1.3 圖示
首先,將入度為0的1,2,4結(jié)點加入隊列,首先遍歷1后的結(jié)點:
此時,結(jié)點3的入度減去1,變?yōu)?,不等于0,先不進行操作,隨后,對2后的結(jié)點進行遍歷:
此時,結(jié)點3的入度減為0,加入隊列。
接著,如下操作:
所有結(jié)點遍歷完畢,其先后順序即為 1 2 4 3 5 6。
1.4 不穩(wěn)定性
拓撲排序無其他排序條件時是不穩(wěn)定的,與你加入隊列的順序與方式有關(guān),比如上面圖示樣例中也可寫成1 4 2 3 5 6,4 1 2 3 5 6等等。
因為其在實際問題中,同層的先后順序是無關(guān)事件,因此不影響其正確性,若對排序有另外的要求,則按照要求的方式加入隊列即可。
1.5 無法排序?
當(dāng)圖是有環(huán)圖,意味著一定有結(jié)點無法將其入度減為0,因此無法進行其后續(xù)結(jié)點的排序,如圖:
此時不存在入度為0的點,因此無法得知其先后順序。
因此,我們需要在進行排序時,判斷所有遍歷過的點數(shù)目是否等于所有點的數(shù)目,若不等于,則表示其存在環(huán),排序失敗,若等于,則排序成功。
1.6 時間復(fù)雜度
因每個結(jié)點只需加入隊列一次,而每個結(jié)點加入隊列前需要進行入度數(shù)量次的操作,因此時間復(fù)雜度為O(n+m)。
2、算法實現(xiàn)
2.1 題目
給你n個有向邊和m個點,若能進行拓撲排序,輸出排序后的序列,若不能,輸出-1.
輸出為1行,答案可能不唯一,輸出正解中任意一種。
2.2 解法分析
首先,記錄所有點的入度,此時設(shè)置數(shù)組(或其他記錄方式)inp[],在每次輸入時,對后一個事件的入度進行加1,并記錄前一個事件的后續(xù)事件:
for(int i = 0; i < n; i++){cin>>a>>b;inp[b]++;v[a].push_back(b); }隨后,將每個入度為0的結(jié)點加入隊列:
queue<int>q; for (int i = 1; i <= m; i++) {//注意點的標(biāo)號從幾開始if (!inp[i])q.emplace(i);//為0則加入隊列 }接著,對圖按照BFS進行遍歷:
while (!q.empty()) {int fa = q.front();q.pop();pd++;//點數(shù)量的記錄ans[num++] = fa;//記錄順序for (auto now : v[fa]) {inp[now]--;//入度減一if (!inp[now]) {//為0則加入隊列q.emplace(now);}} }最后對是否排序成功進行判斷:
if(pd==m)return 1; else return 0;2.3 完整代碼
#include<iostream> #include<algorithm> #include<stdio.h> #include<vector> #include<queue>using namespace std;int inp[1005]; int n, m, pd; vector<int>v[1005]; int ans[1005];bool bfs() {queue<int>q;for (int i = 1; i <= m; i++) {if (!inp[i])q.emplace(i);//為0則加入隊列}while (!q.empty()) {int fa = q.front();q.pop();ans[pd++] = fa;for (auto now : v[fa]) {inp[now]--;//入度減一if (!inp[now]) {//為0則加入隊列q.emplace(now);}}}if (pd == m)return true;else return false; }int main() {int a, b;cin >> n >> m;for (int i = 0; i < n; i++) {cin >> a >> b;inp[b]++;v[a].push_back(b);}if (bfs()) {cout << ans[0];for (int i = 1; i < m; i++){cout << ' ' << ans[i];}}else {cout << -1;}cout << endl;return 0; }樣例輸入1:
5 6 1 3 2 3 3 5 4 5 5 6預(yù)期結(jié)果1:
1 2 4 3 5 6測試結(jié)果1:
樣例輸入2:
6 6 1 2 2 3 3 4 4 5 5 2 4 6預(yù)期結(jié)果2:
-1測試結(jié)果2:
以上樣例均通過。
幾道例題
A:POJ - 2367 Genealogical tree
Description
The system of Martians’ blood relations is confusing enough. Actually, Martians bud when they want and where they want. They gather together in different groups, so that a Martian can have one parent as well as ten. Nobody will be surprised by a hundred of children. Martians have got used to this and their style of life seems to them natural.
And in the Planetary Council the confusing genealogical system leads to some embarrassment. There meet the worthiest of Martians, and therefore in order to offend nobody in all of the discussions it is used first to give the floor to the old Martians, than to the younger ones and only than to the most young childless assessors. However, the maintenance of this order really is not a trivial task. Not always Martian knows all of his parents (and there’s nothing to tell about his grandparents!). But if by a mistake first speak a grandson and only than his young appearing great-grandfather, this is a real scandal.
Your task is to write a program, which would define once and for all, an order that would guarantee that every member of the Council takes the floor earlier than each of his descendants.
Input
The first line of the standard input contains an only number N, 1 <= N <= 100 — a number of members of the Martian Planetary Council. According to the centuries-old tradition members of the Council are enumerated with the natural numbers from 1 up to N. Further, there are exactly N lines, moreover, the I-th line contains a list of I-th member’s children. The list of children is a sequence of serial numbers of children in a arbitrary order separated by spaces. The list of children may be empty. The list (even if it is empty) ends with 0.
Output
The standard output should contain in its only line a sequence of speakers’ numbers, separated by spaces. If several sequences satisfy the conditions of the problem, you are to write to the standard output any of them. At least one such sequence always exists.
Sample Input
5
0
4 5 1 0
1 0
5 3 0
3 0
Sample Output
2 4 5 3 1
分析
此題輸入第i個結(jié)點的后續(xù)結(jié)點,直接建圖拓撲排序即可。
代碼
#include<iostream> #include<algorithm> #include<stdio.h> #include<vector> #include<queue>using namespace std;int inp[1005], n, pd, num; vector<int>v[1005]; int ans[1005];bool bfs() {queue<int>q;for (int i = 1; i <= n; i++) {if (!inp[i])q.emplace(i);//為0則加入隊列}while (!q.empty()) {int fa = q.front();q.pop();ans[pd++] = fa;for(int i = 0; i < v[fa].size(); i++){int now = v[fa][i];inp[now]--;//入度減一if (!inp[now]) {//為0則加入隊列q.emplace(now);}}}if (pd == n)return true;else return false; }int main() {int a;cin >> n;for (int i = 0; i < n; i++) {while (cin >> a, a != 0) {v[i + 1].push_back(a);inp[a]++;}}if (bfs()) {cout << ans[0];for (int i = 1; i < num; i++) {cout << ' ' << ans[i];}}else {cout << -1;}cout << endl;return 0; }B:HDU - 1285 確定比賽名次
Problem Description
有N個比賽隊(1<=N<=500),編號依次為1,2,3,。。。。,N進行比賽,比賽結(jié)束后,裁判委員會要將所有參賽隊伍從前往后依次排名,但現(xiàn)在裁判委員會不能直接獲得每個隊的比賽成績,只知道每場比賽的結(jié)果,即P1贏P2,用P1,P2表示,排名時P1在P2之前。現(xiàn)在請你編程序確定排名。
Input
輸入有若干組,每組中的第一行為二個數(shù)N(1<=N<=500),M;其中N表示隊伍的個數(shù),M表示接著有M行的輸入數(shù)據(jù)。接下來的M行數(shù)據(jù)中,每行也有兩個整數(shù)P1,P2表示即P1隊贏了P2隊。
Output
給出一個符合要求的排名。輸出時隊伍號之間有空格,最后一名后面沒有空格。
其他說明:符合條件的排名可能不是唯一的,此時要求輸出時編號小的隊伍在前;輸入數(shù)據(jù)保證是正確的,即輸入數(shù)據(jù)確保一定能有一個符合要求的排名。
Sample Input
4 3
1 2
2 3
4 3
Sample Output
1 2 4 3
分析:
此題要求隊伍編號小的隊伍在前,因此需要使用優(yōu)先隊列,然后建圖拓撲排序即可。
代碼:
#include<iostream> #include<algorithm> #include<stdio.h> #include<vector> #include<queue> #include<functional>using namespace std;int inp[505], n, m; vector<int>v[505]; int ans[505];void bfs() {//queue<int>q;int pd = 0;priority_queue<int, vector<int>, greater<int>>q;//因題目要求,此處采用優(yōu)先隊列for (int i = 1; i <= n; i++) {if (!inp[i])q.emplace(i);//為0則加入隊列}while (!q.empty()) {int fa = q.top();q.pop();ans[pd++] = fa;for(int i = 0; i < v[fa].size(); i++){int now = v[fa][i];inp[now]--;//入度減一if (!inp[now]) {//為0則加入隊列q.emplace(now);}}} }int main() {int a, b;while (cin>>n>>m) {memset(inp, 0, sizeof inp);memset(v, 0, sizeof v);for (int i = 0; i < m; i++) {cin >> a >> b;inp[b]++;v[a].push_back(b);}bfs();cout << ans[0];for (int i = 1; i < n; i++) {cout << ' ' << ans[i];}cout << '\n';}return 0; }C:HDU - 2094產(chǎn)生冠軍
Problem Description
有一群人,打乒乓球比賽,兩兩捉對撕殺,每兩個人之間最多打一場比賽。
球賽的規(guī)則如下:
如果A打敗了B,B又打敗了C,而A與C之間沒有進行過比賽,那么就認(rèn)定,A一定能打敗C。
如果A打敗了B,B又打敗了C,而且,C又打敗了A,那么A、B、C三者都不可能成為冠軍。
根據(jù)這個規(guī)則,無需循環(huán)較量,或許就能確定冠軍。你的任務(wù)就是面對一群比賽選手,在經(jīng)過了若干場撕殺之后,確定是否已經(jīng)實際上產(chǎn)生了冠軍。
Input
輸入含有一些選手群,每群選手都以一個整數(shù)n(n<1000)開頭,后跟n對選手的比賽結(jié)果,比賽結(jié)果以一對選手名字(中間隔一空格)表示,前者戰(zhàn)勝后者。如果n為0,則表示輸入結(jié)束。
Output
對于每個選手群,若你判斷出產(chǎn)生了冠軍,則在一行中輸出“Yes”,否則在一行中輸出“No”。
Sample Input
3
Alice Bob
Smith John
Alice Smith
5
a c
c d
d e
b e
a d
0
Sample Output
Yes
No
分析:
此題是考察拓撲排序的性質(zhì),即若冠軍只有一人的話,則初始化圖的入度為0的僅有一個結(jié)點,因此直接枚舉入度為0結(jié)點數(shù)量即可。
代碼:
#include<iostream> #include<algorithm> #include<stdio.h> #include<string> #include<map>using namespace std;map<string, int>mp;bool solve(int n) {int poc = 0;for (auto x : mp) {if (!x.second) {poc++;}}if (poc == 1)return 1;elsereturn 0; } int main() {int n;while (cin >> n, n != 0) {mp.clear();string k, l;for (int i = 0; i < n; i++) {cin >> k >> l;mp[k] = mp[k];mp[l]++;}if (solve(n))cout << "Yes\n";elsecout << "No\n";}return 0; }總結(jié)
- 上一篇: 内存插13跟24一样吗
- 下一篇: 2021-08-05学习日记