图论笔记:拓扑排序

本文介绍了图论中的拓扑排序概念及其应用,包括通过BFS和优先队列确定字典序最小的拓扑排序,按字典序输出所有拓扑排序,以及如何利用拓扑排序来检测图中是否存在环。提供了HDU和POJ两个在线判题系统的题目作为实例,详细解析了代码实现过程。
摘要由CSDN通过智能技术生成

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

拓扑排序
维基百科上的解释如下:

当且仅当图中没有定向环时(即有向无环图),才有可能进行拓扑排序。

任何有向无环图至少有一个拓扑排序。已知有算法可以在线性时间内,构建任何有向无环图的拓扑排序。

在图论中,由一个有向无环图的顶点组成的序列,当且仅当满足下列条件时,才能称为该图的一个拓扑排序(英语:Topological sorting):

序列中包含每个顶点,且每个顶点只出现一次;
若A在序列中排在B的前面,则在图中不存在从B到A的路径。


一个图能进行拓扑排序的充要条件是它是一个有向无环图(DAG)
步骤:
基于BFS:
1.找到所有入度为0的点放进队列作为起点谁先谁后没关系
2。弹出队首,其所有邻居点入度减一在次把入度为0的点放入队列
3.直到队列为空
基于DFS:
如果想按正确顺序打印出拓扑序则要用栈


一、 确定字典序最小的拓扑排序

(用邻接矩阵存图)BFS+优先队列(hdu1285)

#include<bits/stdc++.h>//图论拓扑排序+优先队列hdu1285 
using namespace std;
//hdu1285 DFS拓扑排序加优先队列
bool mp[517][517];
int in[517];//记录入度 
priority_queue<int,vector<int>,greater<int> > q;//每次弹出最小值的优先队列 ,得到队伍编号最小的排名 
void tuopu(int n)
{
	for(int i=1;i<=n;i++)
	{
		if(in[i]==0)
			q.push(i);//把入度为0的压入队列 
	}
	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(!mp[v][i])
				continue;
			in[i]--;//入度减一 
			if(!in[i])
				q.push(i);//把入度为0的压入队列 
		}
	}
}
int main()
{
	int n,m,i,j;
	while(cin>>n>>m)
	{
		int k=0;
		memset(mp,0,sizeof(mp));
		memset(in,0,sizeof(in));
		while(m--)
		{
			cin>>i>>j;
			if(mp[i][j])
			continue;
			mp[i][j]=1;
			in[j]++;	
		}	
		tuopu(n);
	}	
}

二, 按字典序从小到大输出所有拓扑排序

poj1270

#include<iostream>//poj1270拓扑排序,邻接矩阵dfs实现,重要 
#include<algorithm>
#include<cstring>
using namespace std;

int mp[30][30],in[30],tp[30],n;
char s[30];
int getit(int ch)
{
	for(int i=1;i<n;i++)
		if(s[i]==ch) return i;
}
bool init()//输入 
{
	char ch;
	n=0;
	while((ch=getchar())!='\n')
	{
		if(ch==EOF) return false;
		if(ch!=' ') s[++n]=ch;
	}
	s[++n]='\0';
	sort(s+1,s+n);
	memset(in,0,sizeof(in));
	memset(mp,0,sizeof(mp));
	while(true)//输入约束对 
	{
		while((ch=getchar())==' ');
		if(ch==EOF) return false;
		if(ch=='\n') break;
		int u=getit(ch);
		while((ch=getchar())==' ');
		int v=getit(ch);
		mp[u][v]=1;//邻接矩阵储存 
		in[v]++;//入度增加	
	} 
	return true;
}
void dfs(int now)
{
	if(now==n) //到结尾 
	{
		for(int i=1;i<n;i++)
		{
			cout<<s[tp[i]];//输出拓扑序 
		}
		cout<<endl;
		return;
	}
	for(int i=1;i<n;i++)
	{
		if(in[i]==0)
		{
			in[i]=-1;
			tp[now]=1;//入度为0的在第一位 
			for(int j=1;j<n;j++)
				if(mp[i][j]) in[j]--;
				//把该结点的子节点入度递减 
			dfs(now+1);
			in[i]=0;//恢复现场 
			for(int j=1;j<n;j++)
				if(mp[i][j]) in[j]++;
		}
	}
}
void work()
{
	while(init())
	{
		dfs(1);
		cout<<endl;
	}
}
int main()
{
	work();
	return 0;
} 

三,拓扑序找环,判断是否有环

hdu3342
 如果队列已空但是还有点未进入队列则这些点入度都不是0,说明图不是DAG

#include<bits/stdc++.h>//hdu3342拓扑序找环 
using namespace std;
int n;
bool m[105][105];//邻接矩阵储存
int master[105];//指向的徒弟的师傅数量(入度)
int main()
{
	int n,M,u,v;
	while(scanf("%d%d",&n,&M)!=EOF and n)	
	{
		memset(master,0,sizeof(master));
		memset(m,false,sizeof(m));
		while(M--)
		{
			scanf("%d%d",&u,&v);
			if(!m[u][v])
			{
				m[u][v]=true;
				++master[v];
			}
		}
		bool flag;
		int num=0;//人数
		for(;;)
		{
			flag=true;
			for(int u=0;u<n;u++)//找出master数量为0的人 
			{
				if(master[u]==0){
					num++;
					master[u]=-1;
					flag=false;
					for(int v=0;v<n;v++)
					{
						if(m[u][v]) master[v]--;//入度递减			 
					}
				}
			}
			if(flag) break;	
		} 
		if(num==n) cout<<"YES"<<endl;
		else cout<<"NO"<<endl;
	}
	return 0;
} 

四,判断有无环+反向建图

hdu2647

#include<bits/stdc++.h>//hdu3342拓扑序找环 
using namespace std;
int n;
bool m[105][105];//邻接矩阵储存
int master[105];//指向的徒弟的师傅数量(入度)
int main()
{
	int n,M,u,v;
	while(scanf("%d%d",&n,&M)!=EOF and n)	
	{
		memset(master,0,sizeof(master));
		memset(m,false,sizeof(m));
		while(M--)
		{
			scanf("%d%d",&u,&v);
			if(!m[u][v])
			{
				m[u][v]=true;
				++master[v];
			}
		}
		bool flag;
		int num=0;//人数
		for(;;)
		{
			flag=true;
			for(int u=0;u<n;u++)//找出master数量为0的人 
			{
				if(master[u]==0){
					num++;
					master[u]=-1;
					flag=false;
					for(int v=0;v<n;v++)
					{
						if(m[u][v]) master[v]--;//入度递减			 
					}
				}
			}
			if(flag) break;	
		} 
		if(num==n) cout<<"YES"<<endl;
		else cout<<"NO"<<endl;
	}
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

June_gjy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值