算法竞赛备赛——【图论】拓扑排序

发布于:2025-07-24 ⋅ 阅读:(18) ⋅ 点赞:(0)

拓扑排序算法

前置知识:

1.DAG图:一个无环的有向图,即有向无环图。

2.AOV网络:在⼀个表示⼯程的有向图中,⽤顶点表示活动,⽤弧表示活动之间的优先关系的有向图称为顶点表示活动的⽹(Activity On Vertex Network),简称AOV⽹。

拓扑排序:其实就是对⼀个DAG图构造拓扑序列的过程。

拓扑排序算法:kahn(卡恩)算法(基于BFS)和 基于DFS的算法。

kahn(卡恩)算法

可以判环

时间复杂度:O(V+E)

有环就没有拓扑序列

#include<bits/stdc++.h>
using namespace std;
using ll=long long;

//有向图------>判环 
int n,m;
struct Edge{//只求拓扑序列不用带边权 
	int to,next;
}e[10005];
int h[105];
int ind[105];//入度 
int cnt;
int topo[105],k=0;

void add(int u,int v){
	e[cnt].to=v;
	e[cnt].next=h[u];
	h[u]=cnt;
	cnt++; 
} 

bool kahn(){
	queue<int> q;
	for(int i=1;i<=n;++i){
		if(ind[i]==0){
			q.push(i);
		}
	}
	int x,y;
	while(!q.empty()){
		x=q.front();
		q.pop();
		topo[++k]=x;//从1开始计数 
		for(int i=h[x];i!=-1;i=e[i].next) {
			y=e[i].to;
			ind[y]--;
			if(ind[y]==0) q.push(y); 
		}
	}
	if(k==n) return true;//无环 
	else return false;//成环 
} 

int main(){
	cin>>n>>m;
	memset(h,-1,sizeof h);
	int u,v;
	for(int i=1;i<=m;++i){
		cin>>u>>v;
		add(u,v);
		ind[v]++;
	}
	if(kahn()){
		for(int i=1;i<=k;++i){
			cout<<topo[i]<<" ";
		}
	} else{
		cout<<"有环\n"; 
	}
	return 0;
} 

例题

P1113 杂务 - 洛谷

#include<bits/stdc++.h>
using namespace std;
using ll=long long;
int n; 
struct Edge{
	int to,next;
}e[100005];
int h[10005],cnt;
int ind[10005];
int t[10005];//每个点需要的时间 
int ed[10005];//每个点的完成时间 

void add(int u,int v){
	e[cnt].to=v;
	e[cnt].next=h[u];
	h[u]=cnt;
	cnt++;
}

void kahn(){
	queue<int> q;
	for(int i=1;i<=n;++i){
		if(ind[i]==0){
			q.push(i);
			ed[i]=t[i];//没有前置活动 
		}
	}
	int u,v;
	while(!q.empty()){
		u=q.front();
		q.pop();
		for(int i=h[u];~i;i=e[i].next){
			v=e[i].to;
			ind[v]--;
			ed[v]=max(ed[v],ed[u]+t[v]); //更新y的结束时间 
			if(ind[v]==0){
				q.push(v);
			}
		} 
	}
}

int main(){
	cin>>n;
	int u,v;
	memset(h,-1,sizeof h);
	for(int i=1;i<=n;++i){
		cin>>u;
		cin>>t[u];//注意不要同行读入
		while(cin>>v){
			if(v==0) break;
			add(u,v);
			ind[v]++;
		}
	}
	kahn();
	int ans=-1;
	for(int i=1;i<=n;++i){
		ans=max(ans,ed[i]);
	} 
	cout<<ans<<endl;
	return 0;
} 

[P1983 NOIP 2013 普及组] 车站分级 - 洛谷

#include<bits/stdc++.h>
using namespace std;
using ll=long long;
int n,m; 
vector<int> g[1005];
int s[1005];
int flag[1005];
int pd[1005][1005];//判断i--> j 的边是否已经建立
int ind[1005]; 
int le[1005];//级别 

void kahn(){
	queue<int> q;
	for(int i=1;i<=n;++i){
		if(ind[i]==0){
			q.push(i);
			le[i]=1;
		}
	}
	int x,y;
	while(!q.empty()){
		x=q.front();
		q.pop();
		for(int i=0;i<g[x].size();i++){
			y=g[x][i];
			le[y]=max(le[y],le[x]+1);
			ind[y]--;
			if(ind[y]==0){
				q.push(y);
			}
		}
	}
}

int main(){
	cin>>n>>m;
	int sn;
	for(int i=1;i<=m;++i){
		cin>>sn;//第i趟线路停靠sn个站 
		memset(flag,0,sizeof(flag));
		for(int j=1;j<=sn;++j){
			cin>>s[j];
			flag[s[j]]=1;//s[j]站停靠了 
		}
		for(int j=s[1];j<=s[sn];j++){//枚举该线路中所有没停靠的站 
			if(flag[j]==1) continue;
			for(int k=1;k<=sn;k++){
				if(pd[j][s[k]]==0){//避免重复建边 
					g[j].push_back(s[k]);
					pd[j][s[k]]=1;
					ind[s[k]]++;
				}
			} 
		}
		
	}
	kahn();
	int ans=-1;
	for(int i=1;i<=n;++i){
		ans=max(ans,le[i]);
	}
	cout<<ans;
	return 0;
} 

基于DFS的算法

时间复杂度:O(V+E)

直接求

#include<bits/stdc++.h>
using namespace std;
using ll=long long;

//有向图------>保证是DAG图 无环 
int n,m;
struct Edge{//只求拓扑序列不用带边权 
	int to,next;
}e[10005];
int h[105];
int ind[105];//入度 
int cnt;
stack<int> topo; 
int vis[105];

void add(int u,int v){
	e[cnt].to=v;
	e[cnt].next=h[u];
	h[u]=cnt;
	cnt++; 
} 

void DFS(int x){
	vis[x]=1;
	int y;
	for(int i=h[x];~i;i=e[i].next){
		y=e[i].to;
		if(vis[y]==0){
			DFS(y);
		}
	}
	topo.push(x);//返回时入栈 
}

int main(){
	cin>>n>>m;
	memset(h,-1,sizeof h);
	int u,v;
	for(int i=1;i<=m;++i){
		cin>>u>>v;
		add(u,v);
		ind[v]++;
	}
	for(int i=1;i<=n;++i){
		if(vis[i]==0){
			DFS(i);
		}
	}
	while(!topo.empty()){
		cout<<topo.top()<<" ";
		topo.pop();
	} 
	return 0;
} 

可以判环

#include<bits/stdc++.h>
using namespace std;
using ll=long long;

//有向图------>判环 
int n,m;
struct Edge{//只求拓扑序列不用带边权 
	int to,next;
}e[10005];
int h[105];
int ind[105];//入度 
int cnt;
stack<int> topo; 
int vis[105];
//vis[i]=0  还未走过/还未遍历过
//      =1  该点及其后面所有的点已经访问完了---->回溯时标记为1 
//      =2  这个点走过一次了 现在正在遍历该点后面的点---->第一次访问标记为2 
int flag=0;//标记是否有环 

void add(int u,int v){
	e[cnt].to=v;
	e[cnt].next=h[u];
	h[u]=cnt;
	cnt++; 
} 

void DFS(int x){
	vis[x]=2;
	int y;
	for(int i=h[x];~i;i=e[i].next){
		y=e[i].to;
		if(vis[y]==2){
			flag=1;
			//ans=y;  //可以用ans来记录环从哪里开始 
			return ;
		} 
		else if(vis[y]==0){
			DFS(y);
			if(flag==1){
				return ;
			}
		}
	}
	topo.push(x);//返回时入栈 
	vis[x]=1; //x及其后面的点访问结束 
}

int main(){
	cin>>n>>m;
	memset(h,-1,sizeof h);
	int u,v;
	for(int i=1;i<=m;++i){
		cin>>u>>v;
		add(u,v);
		ind[v]++;
	}
	for(int i=1;i<=n;++i){
		if(vis[i]==0){
			DFS(i);
		}
	}
	if(flag==1){
		cout<<"有环"<<endl;
		return 0; 
	} 
	while(!topo.empty()){
		cout<<topo.top()<<" ";
		topo.pop();
	} 
	return 0;
} 

网站公告

今日签到

点亮在社区的每一天
去签到