图论(1) 拓扑排序

AOV网

概念:用于表示子任务先后顺序的有向无环图

构造拓扑序列步骤

  1. 从图中选择一个入度为零的点。

  2. 输出该顶点,从图中删除此顶点及其所有的出边。

应用:可用于解决某些具有先后关系的图或具有明显分级的数列,可将数列分级建图后拓扑排序查找答案

Kahn 算法

思路

不断完成并更新前驱事件全部完成的节点。

算法过程

  1. 初始时将所有入度为 \(0\) 的点加入 \(S\) 集合。

  2. 任意取出一个点,标记该点已被遍历,将所有以该点为起点的边删除(代码中体现为终点入度减一)。

  3. 将新出现的入度为 \(0\) 的点加入集合。

  4. 重复步骤 \(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到末尾后,保证了该点的所有前驱节点均在他之后加入答案序列,即输出答案时前驱节点必然被更早的输出(即该节点被完成),故保证了该序列必然成立。

算法过程

  1. 对图中的每个顶点,调用DFS函数。

  2. 在DFS函数中,标记当前顶点为已访问。

  3. 对当前顶点的每个未访问的邻接点,递归调用DFS函数。

  4. 当DFS函数返回时,将当前顶点添加到拓扑排序的列表中。

  5. 最终,拓扑排序的列表逆序即为结果。

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;
}

posted @ 2025-05-02 10:27  -Delete  阅读(18)  评论(0)    收藏  举报