一、概念
定義
對一個有向無環圖 ( Directed Acyclic Graph 簡稱 DAG ) G 進行拓撲排序,是將 G中所有頂點排成一個線性序列,使得圖中任意一對頂點 u 和 v ,若邊 < u , v > ∈ E ( G ),則 u 在線性序列中出現在 v之前。通常,這樣的線性序列稱為滿足拓撲次序 ( Topological Order )的序列,簡稱拓撲序列。簡單的說,由某個集合上的一個偏序得到該集合上的一個全序,這個操作稱之為拓撲排序。
簡單來說拓撲排序其實就是一種依賴關系:
- 每個頂點出現且只出現一次。
- 若存在一條從頂點 A 到頂點 B 的路徑,那么在序列中頂點 A 出現在頂點 B 的前面。
- 有向無環圖(DAG)才有拓撲排序,非DAG圖沒有拓撲排序一說。(拓撲排序判斷是否有環)
- 通常,一個有向無環圖可以有一個或多個拓撲排序序列
有向無環圖 = > =>=> 拓撲排序 = > =>=> 拓撲圖 = > =>=>每一個狀態都沒有循環依賴= > =>=>沒有后效性= > =>=>可以DP / 遞推求解答案(比如最短 / 長路)
二、算法的實現
基本思路
在一個有向圖中,對所有的節點進行排序,要求沒有一個節點指向它前面的節點。
1、先統計所有節點的入度,對于入度為0的節點就可以分離出來,然后把這個節點指向的節點的入度減一。
2、一直做改操作,直到所有的節點都被分離出來。
如果最后不存在入度為0的節點,那就說明有環,不存在拓撲排序,也就是很多題目的無解的情況。
實現步驟
1.在該圖中選擇一個沒有前驅(即入度為0)的頂點并記錄該頂點;
2.從圖中刪除該頂點和所有以它為起點的有向邊;
3.重復步驟1和步驟2,直到當前的圖為空或者當前圖中不存在沒有前驅(入度為0)的頂點為止,如果滿足后一種情況說明有向圖必然存在環.
例題
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
#define ll long long
#define ull unsigned long long
#define pii pair<int, int>
#define endl '\n'
#define mem(a, b) memset(a, b, sizeof(a))
#define Max(x, y) (x > y ? x : y)
#define fi(front, end) for (int i = front; i < end; i++)
#define fj(front, end) for (int j = front; j < end; j++)
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0); //勿混用
//#define MAX 0x3f3f3f3f
#define N 100000
int n, idx;
int in[N], head[N]; //記錄每個點的入度
struct A
{
int qi, ed, next;
}ded[N];
void add(int a, int b)
{
ded[idx].ed = b;
ded[idx].qi = a;
ded[idx].next = head[a];
head[a] = idx++;
}
void solve()
{
vector<int>ans;
priority_queue<int, vector<int>, greater<int> >que;
fi (1, n+1) //將所有入度為0的點加入隊列
if (!in[i])
que.push(i);
int cnt = 1;
while (!que.empty())
{
int t = que.top();
que.pop();
ans.push_back(t);
for (int i = head[t]; i != -1; i = ded[i].next)
{
int ed = ded[i].ed;
in[ed]--;
if (!in[ed]) que.push(ed);
}
}
if (ans.size() == n)
{
bool loop = true;
for (int i : ans)
{
if (loop)
loop = false, cout << i ;
else
cout << ' ' << i;
}
cout << endl;
}
}
int main()
{
IOS
int m, a, b;
while (cin >> n >> m)
{
mem(head, -1);
mem(in, 0);
fi(0, m)
{
cin >> a >> b;
add(a, b);
in[b]++;
}
solve();
}
return 0;
}
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
#define ll long long
#define ull unsigned long long
#define pii pair<int, int>
#define endl '\n'
#define mem(a, b) memset(a, b, sizeof(a))
#define Max(x, y) (x > y ? x : y)
#define fi(front, end) for (int i = front; i < end; i++)
#define fj(front, end) for (int j = front; j < end; j++)
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0); //勿混用
//#define MAX 0x3f3f3f3f
#define N 200050
int n, idx;
int in[N], head[N], d[N]; //記錄每個點的入度
struct A
{
int qi, ed, next;
}ded[N];
void add(int a, int b)
{
ded[idx].ed = b;
ded[idx].qi = a;
ded[idx].next = head[a];
head[a] = idx++;
}
int solve()
{
fi (0,n+5) d[i] = 888;
queue<int>que;
fi (1, n+1) //編號
if (!in[i])
que.push(i);
int cnt = 0, ans = 0;
while (!que.empty())
{
int t = que.front();
que.pop();
cnt++;
for (int i = head[t]; i != -1; i = ded[i].next)
{
int ed = ded[i].ed;
in[ed]--;
if (!in[ed])
{
que.push(ed);
d[ed] = d[t]+1;
}
}
}
if (cnt == n)
{
fi (1,n+1)
ans += d[i];
return ans;
}
return -1;
}
int main()
{
IOS
int m, a, b;
while (cin >> n >> m)
{
if (n == 0 && m == 0) break;
mem(head, -1);
mem(in, 0);
fi(0, m)
{
cin >> a >> b;
add(b, a);
in[a]++;
}
cout << solve() << endl;
}
return 0;
}