拓撲排序((算法競賽入門經典)劉汝佳)
轉載請註明出處:http://blog.csdn.net/u012860063?
viewmode=contents
【分析】(小白)
把每一個變量看成一個點,“小於”關系看成有向邊,則我們得到了一個有向圖。這樣,我們的任務實際上是把一個圖的全部結點排序,使得每一條有向邊(u,v)相應的u都排在v的前面。在圖論中,這個問題稱為拓撲排序。
不難發現:假設圖中存在有向環,則不存在拓撲排序,反之則存在。我們把不包括有向環的有向圖稱為有向無環圖。能夠借助dfs函數完畢拓撲排序:在訪問完一個結點之後把它加到當前拓撲排序的首部。
一、輸出隨意一種滿足優先條件的就可以:
代碼例如以下:
#include <cstdio> #include <cstring> #define MAXN 517 int G[MAXN][MAXN]; int c[MAXN],topo[MAXN]; int t; int n, m; bool dfs(int u) { c[u] = -1; for(int v = 1; v <= n; v++) { if(G[u][v]) { if(c[v] < 0) return false; else if(!c[v] && !dfs(v)) return false; } } c[u] = 1; topo[--t] = u; return true; } bool toposort() { t = n; memset(c,0,sizeof(c)); for(int u = 1; u <= n; u++) { if(!c[u]) { if(!dfs(u)) return false; } } return true; } int main() { int a, b; int i; while(~scanf("%d%d",&n,&m)) { for(i = 0; i < m; i++) { scanf("%d%d",&a,&b); G[a][b] = 1; } if(toposort()) { bool ok = true; for(i = 0; i < n; i++) { if(ok) { printf("%d",topo[i]); ok = false; } else printf(" %d",topo[i]); } } printf("\n"); } return 0; }
這裏用到了一個c數組,c[u]=0表示從來沒有訪問過(從來沒有調用過dfs(u));c[u]=1表示已經訪問過。而且還遞歸訪問過它的全部子孫(即dfs(u)曾被調用過。並已返回)。c[u]=-1表示正在訪問(即遞歸調用dfs(u)正在棧幀中,尚未返回)。
此時須要用到優先隊列;
拓撲排序
首先統計每一個結點的入度。將度為0的結點編號放入隊列(此題放入優先隊列中)中。 然後進行循環:- 取出隊頭結點,視作邊的起點。
- 然後“刪除與該點相連的邊”,代碼就是將這個圖中的該邊還有一個結點(即終點)的入度減一。
- 假設減一以後,終點的入度變為了0,那麽將終點的編號入隊列
- 推斷隊列是否為空。若不空,則回到1
優先隊列
C++ STL中有優先隊列的類——priority_queue<T>。默認優先隊列是值越大,優先級越高。所以比方priority_queue<int> q。這裏面的元素是降序排列的。
假設我們要實現升序須要重載。
priority_queue<int,vector<int>,greater<int> > q;
代碼例如以下:(HDU1285)
#include<iostream> #include<queue> #include<cstdio> #include<cstring> using namespace std; bool map[517][517]; int in[517]; priority_queue<int,vector<int>,greater<int> > q; void toposort(int n) { for(int i=1;i<=n;i++) { if(in[i]==0)//入度為0的放入隊列中 q.push(i); } int c=1; while(!q.empty()) { int v=q.top(); q.pop(); if(c!=n) { cout<<v<<" "; c++; } else cout<<v<<endl; for(int i=1;i<=n;i++) { if(!map[v][i]) continue; in[i]--; if(!in[i])//入度為0的放入隊列 q.push(i); } } } int main() { int n,m,i,j; while(cin>>n>>m) { int k=0; memset(map,0,sizeof map); memset(in,0,sizeof in); while(m--) { cin>>i>>j; if(map[i][j])//去重 continue; map[i][j]=1; in[j]++; } toposort(n); } }
三、直接每次先輸出入度為零的點(HDU1285):http://acm.hdu.edu.cn/showproblem.php?pid=1285
和二是一樣的思路,僅僅是沒有使用優先隊列。而是使用了數組;
代碼例如以下:
#include <cstdio> #include <cstring> #define MAXN 517 int G[MAXN][MAXN];//路徑 int in_degree[MAXN];//入度 int ans[MAXN]; int n, m, x, y; int i, j; void toposort() { for(i = 1; i <= n; i++) { for(j = 1; j <= n; j++) { if(G[i][j]) { in_degree[j]++; } } } for(i = 1; i <= n; i++)//從最小的開始尋找, {//這樣保證了有多個答案時序號小的先輸出 int k = 1; while(in_degree[k] != 0)//尋找入度為零的點 k++; ans[i] = k; in_degree[k] = -1; //更新為-1,後邊檢測不受影響,相當於刪除節點 for(int j = 1; j <= n; j++) { if(G[k][j]) in_degree[j]--;//相關聯的入度減1 } } } void init() { memset(in_degree,0,sizeof(in_degree)); memset(ans,0,sizeof(ans)); memset(G,0,sizeof(G)); } int main() { while(~scanf("%d%d",&n,&m)) { init(); for(i = 0; i < m; i++) { scanf("%d%d",&x,&y); G[x][y] = 1; } toposort(); for(i = 1; i < n; i++) printf("%d ",ans[i]); printf("%d\n",ans[n]); } return 0; }
以上解說的都是二維的用的是鄰接矩陣存圖,僅僅適用於稠密圖,數據過大就不能存了。
以下是用鄰接表存圖的拓撲排序, 適用於大數據。稀疏圖!
一、數組式鄰接表:
代碼例如以下:
#include <iostream> using namespace std; int ind[517]; // indegree入度個數 int adj[250017]; //adjacency list鄰接表位置值 int adj_next[250017];//鄰接表下一指針 int tail[517]; //鄰接表尾 int main() { int n,m,i,j,a,b; while(scanf("%d%d",&n,&m)!=EOF) { for(i = 0; i <= n; i++) { tail[i] = -1; adj[i] = -1; adj_next[i] = -1; ind[i] = 0; } for(i = 0; i < m; ++i) { scanf("%d%d",&a,&b); int x = tail[a],flag = 0; while(x != -1) //推斷是否重邊 { if(adj[x] == b) { flag = 1; break; } x = adj_next[x]; } if(!flag)//關聯a的鄰接表 { adj[i] = b; adj_next[i] = tail[a]; tail[a] = i; ind[b] ++; } } for(i = 1;i <= n; i++)//找n次 { for(j = 1;j <= n;j++)//遍歷 { if(ind[j] == 0)//當入度為0時。說明靠前 { ind[j] = -1;//在下次尋找入度為0時跳過 if(i == 1) printf("%d",j); else printf(" %d",j); for(int k = tail[j]; k != -1; k = adj_next[k])//鄰接位置入度減一 { ind[adj[k]]--; } break; } } } printf("\n"); } return 0; }
二、結構體(鏈表)式鄰接表:
代碼例如以下:
#include<iostream> #include<cstring> #include<cstdlib> #include<cmath> #include<cstdio> #include<queue> #include<bitset> using namespace std; #define maxn 505 struct node { int num; node *next; }; node map[maxn]; int d[maxn], n; void Insert(int a, int b); void Free(); void Topsort(); int main() { int m; while(cin >> n >> m) { int i, a, b; memset(d, 0, sizeof(d)); for(i=0; i<m; i++) { cin >> a >> b; Insert(a, b); d[b]++; } Topsort(); Free(); } return 0; } void Insert(int a, int b) { node *newnode; newnode = (node *)malloc(sizeof(node)); newnode->num = b; newnode->next = map[a].next; map[a].next = newnode; } void Free() { node *cur, *old; for(int i=1; i<=n; i++) { cur = map[i].next; while(cur) { old = cur; cur = cur->next; free(old); } map[i].next = NULL; } } void Topsort() { priority_queue<int, vector<int>, greater<int> > que; int nx, i; int q[maxn]={0}, k=0; node *cur; for(i=1; i<=n; i++) { if(d[i] == 0) que.push(i); } while(que.size()) { nx = que.top(), que.pop(); q[k++] = nx; cur = map[nx].next; while(cur) { nx = cur->num; d[nx] -= 1; if(d[nx] == 0) que.push(nx); cur = cur->next; } } cout << q[0]; for(i=1; i<k; i++) cout <<" "<< q[i]; cout <<endl; }
還有一種(較特殊):
逆排序+優先隊列;(HDU4587):http://acm.hdu.edu.cn/showproblem.php?pid=4857
也就是HDU第一次BC的第一題!
要使小的序號盡量靠前。小的序號盡量靠前並非代表字典序。要求多種情況時,先使1靠前(可能1僅僅能在第2或第3位 那麽就要使它在第2位),其次2。3。。而不是在當前情況下,該位最小是哪個就輸出哪個;
代碼例如以下:
#include <cstdio> #include <iostream> #include <algorithm> #include <cstring> #include <queue> #include <vector> using namespace std; #define N 30017 int n, m; int i, j, k; int v[N],ans[N]; vector<int>P[N]; void init() { memset(ans,0,sizeof(ans)); memset(v,0,sizeof(v)); } void Topsort() { priority_queue<int>Q; int size, t; for(i = 1; i <= n; i++) { if(v[i] == 0) Q.push(i); } while(!Q.empty()) { t = Q.top(); Q.pop(); size = P[t].size(); for(i = 0; i < size; i++)//相關聯的入度減1 { v[P[t][i]]--; if(v[P[t][i]] == 0) Q.push(P[t][i]); } ans[k++] = t; } } int main() { int T; int a, b; scanf("%d",&T); while(T--) { init(); scanf("%d%d",&n,&m); for(i = 1; i <= n; i++)//清空 { P[i].clear(); } for(i = 0; i < m; i++) { scanf("%d%d",&a,&b); v[a]++; P[b].push_back(a);//逆向建圖,在b後面加入a } k = 0; Topsort(); for(i = n-1; i > 0; i--)//逆向輸出 { printf("%d ",ans[i]); } printf("%d\n",ans[0]); } return 0; }
拓撲排序((算法競賽入門經典)劉汝佳)