图论(1) 拓扑排序
AOV网
概念:用于表示子任务先后顺序的有向无环图
构造拓扑序列步骤:
-
从图中选择一个入度为零的点。
-
输出该顶点,从图中删除此顶点及其所有的出边。
应用:可用于解决某些具有先后关系的图或具有明显分级的数列,可将数列分级建图后拓扑排序查找答案
Kahn 算法
思路
不断完成并更新前驱事件全部完成的节点。
算法过程
-
初始时将所有入度为 \(0\) 的点加入 \(S\) 集合。
-
任意取出一个点,标记该点已被遍历,将所有以该点为起点的边删除(代码中体现为终点入度减一)。
-
将新出现的入度为 \(0\) 的点加入集合。
-
重复步骤 \(2\) \(3\) 直到集合为空(全部遍历完或该图为森林或存在环)
Code.
#include<bits/stdc++.h>
#define endl "\n"
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
//value
const int inf=2147483647;
const int mod=1e9+7;
int cnt[200005];
vector<int>g[200005],ans;
queue<int>q;
//如要求字典序最小可用priority_queue
//function
void solve(){
return;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
int n;
cin>>n;
for(int i=1;i<=n;i++){
int t;
cin>>t;
while(t!=0){
g[i].push_back(t);
cnt[t]++;
cin>>t;
}
}
for(int i=1;i<=n;i++){
if(cnt[i]==0)q.push(i);
}
while(!q.empty()){
int tmp=q.front();
q.pop();
for(auto i:g[tmp]){
cnt[i]--;
if(cnt[i]==0)q.push(i);
}
ans.push_back(tmp);
}
for(int i=0;i<n;i++)cout<<ans[i]<<' ';
return 0;
}
DFS 算法
思路
出度为 \(0\) 的点显然可以放在拓扑排序的最后,回退查找该点的前驱节点是否可以作为后更新的点,所得序列为倒序。
正确性证明:由于dfs到末尾后,保证了该点的所有前驱节点均在他之后加入答案序列,即输出答案时前驱节点必然被更早的输出(即该节点被完成),故保证了该序列必然成立。
算法过程
-
对图中的每个顶点,调用DFS函数。
-
在DFS函数中,标记当前顶点为已访问。
-
对当前顶点的每个未访问的邻接点,递归调用DFS函数。
-
当DFS函数返回时,将当前顶点添加到拓扑排序的列表中。
-
最终,拓扑排序的列表逆序即为结果。
Code.
#include<bits/stdc++.h>
#define endl "\n"
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
//value
const int inf=2147483647;
const int mod=1e9+7;
int vis[200005];
vector<int>g[200005];
stack<int>st;
//function
void dfs(int x){
if(vis[x]==1)return;
vis[x]=1;
for(auto i:g[x]){
dfs(i);
}
st.push(x);
}
void solve(){
return;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
int n;
cin>>n;
for(int i=1;i<=n;i++){
int t;
cin>>t;
while(t!=0){
g[i].push_back(t);
cin>>t;
}
}
for(int i=1;i<=n;i++){
if(!vis[i])dfs(i);
}
while(!st.empty()){
cout<<st.top()<<' ';
st.pop();
}
return 0;
}
题目训练
P1983 [NOIP 2013 普及组] 车站分级
Solution
显然在同一条线路中,出现的数字一定大于未出现的,可以根据大小关系建图后用拓扑思想一层一层删边删点求出层数。只需单开一个数组记录该点在被遍历到时前方最大遍历层数,即可求出答案。
需注意的是,在建图时,注意不要向 \(vector\) 里重复加边,否则会导致不必要的时间空间消耗。
Code.
#include<bits/stdc++.h>
#define endl "\n"
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
//value
const int inf=2147483647;
const int mod=1e9+7;
int g[2005][2005];
int ans[2005],cnt[2005],x[2005];
int q[2005],tot;
//function
void solve(){
return;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
int n,m;
cin>>n>>m;
//建图
while(m--){
int t;
cin>>t;
for(int i=1;i<=t;i++)cin>>x[i];
for(int i=1;i<t;i++){
for(int j=x[i]+1;j<x[i+1];j++){
for(int k=1;k<=t;k++){
if(!g[x[k]][j]){
g[x[k]][j]=1;
cnt[j]++;
}
}
}
}
}
//加队列
for(int i=1;i<=n;i++){
if(cnt[i]==0){
q[++tot]=i;
ans[i]=1;
}
}
//拓扑排序
int fr=1;
while(fr<=tot){
int tmp=q[fr++];
for(int i=1;i<=n;i++){
if(g[tmp][i]==0)continue;
cnt[i]--;
if(cnt[i]==0){
q[++tot]=i;
ans[i]=ans[tmp]+1;
}
}
}
//寻找答案
int ma=0;
for(int i=1;i<=n;i++)ma=max(ma,ans[i]);
cout<<ma<<endl;
return 0;
}

浙公网安备 33010602011771号