zoukankan      html  css  js  c++  java
  • 拓扑排序

     

    拓扑排序问题的特征是有较多的先后顺序事件然后需要求总的事件顺序等,比如排有先修课要求的课表等,经常需要将他们抽象为图解决问题。
    为了节省空间,在不适用STL(其实是不会用)的情况下经常使用用结构体和数组模拟实现的邻接表来存储图

    struct node
    {
    	int from,to,next,value;
    }E[MAXM];
    int head[MAXN];//存储以该节点为开头的边
    int edge=0; //保存边的个数
    int in[MAXN]; //保存入度
    
    void addedge(int u,int v)
    {
    	edge++;
    	E[edge].to=v;
    	E[edge].naxt=head[u];//将这个边的下一个的指针设置为起点的前一个边
    	head[u]=edge;//将起点的边更新;
    }
    

    将图存储进来以后就可以进行拓扑排序了
    一般有两种进行拓扑排序的方法
    第一种是每次查找入度为0的结点依次入栈,实现代码如下

    bool topsort()
    {
    	int s[MAXN]; int top=-1; //用于保存入度为0的结点的栈,尽量定义在外面
    	for(int i=1;i<=n;i++)
    	{
    		for(int j=head[i];j!=0;j=E[j].next)
    		{
    			in[E[j].to]++; //将每个结点为起点的边遍历,然后将尾部结点的入度加一
    		}//最好还是在读入数据的时候同时更新入度,这样更新容易写错(刚开始的时候一直写错没发现i和j的差别,差点自闭)
    	}
    	for(int i=1;i<=n;i++)
    	{
    		if(in[i]==0) s[++top]=i;  //第一遍将入度为0的结点全部入栈
    	}
    	int cnt=0;  //用于记录当前处理到第几个结点
    	while(top!=-1)
    	{
    		int x=s[top--];  //出栈
    		cnt++;
    		ans[cnt]=x; //可以选择记录,也可以干其他的
    		for(int i=head[x];i!=0;i=E[i].next)  //遍历以该节点为起点的边的尾节点,并将它们的入度全部更新
    		{
    			int v=E[i].to;      
    			if(--in[v]==0) s[++top]=v;  //在更新的同时将入度变为0的结点入栈
    		}
    	}
    	return cnt==n;//如果没有环(DAG)必定能够进行拓扑排序,如果有环就会因为总存在入度不为0的结点而不能遍历每一个结点
    }
    

    这种进行拓扑排序的方法还是比较简单的,需要注意的问题是有时候并不能用这种方法发进行拓扑排序,例如CodeForces - 770C ,虽然他们可以像其他问题一样对课程进行拓扑排序,但是他可能并不需要上某些课程,即某些节点可能并不需要,我尝试用这种方法进行拓扑排序:用一个数组保存需要进行排序的点,然后将它后面的点的状态全部变成需要排序的点的状态,这种方法似乎看起来可行,但是问题是如果我需要的那个点在一个环中或者以环为起点的边的尾部时,因为它的入度总不为0,所以总是遍历不到就会结束返回有环错误,但是其实我们并不需要前面有环的部分所以实际上是有结果的。面对这种情况只能用第二种进行拓扑排序的方法,即大家耳熟能详的dfs

    去掉保存入度的数组,新增一个反应结点状态的数组vis[MAXN];
    bool dfs(int u)
    {
    	vis[u]=-1;
    	for(int i=head[u];i!=0;i=E[i].next)
    	{
    		if(vis[i]==-1) return false;  //表示搜索到了结点本身,肯定形成了环
    		if(!vis[i] && !dfs(v)) return false;//如果该节点没有访问过就进行访问,如果访问形成了环返回错误
    	}
    	vis[u]=1; order[--t]=u; //正常结束,将这个点标记为已经访问过,然后用数组记录答案
    	return true;		//首先返回的是出度为0的结点,所以反序
    }
    bool topsort()
    {
    	t=n;
    	memset(vis,0,sizeof(vis));
    	for(int i=1;i<=n;i++)
    	{
    		if(!vis[i] && !dfs(i)) 
    		return false;  //如果这个结点还没有访问过
    	}
    	如果最后所有结点访问完了还没有出现环说明拓扑排序成功
    	return true;
    }
    

    需要注意的除了有时候并不需要所有点外,有时候还要反向建图优先选择编号较大的进行拓扑(需要将编号较小的优先放到前面时)
    例如 :逃生 HDU - 4857
    题意如下:糟糕的事情发生啦,现在大家都忙着逃命。但是逃命的通道很窄,大家只能排成一行。
    现在有n个人,从1标号到n。同时有一些奇怪的约束条件,每个都形如:a必须在b之前。
    同时,社会是不平等的,这些人有的穷有的富。1号最富,2号第二富,以此类推。有钱人就贿赂负责人,所以他们有一些好处。
    负责人现在可以安排大家排队的顺序,由于收了好处,所以他要让1号尽量靠前,如果此时还有多种情况,就再让2号尽量靠前,如果还有多种情况,就让3号尽量靠前,以此类推。
    那么你就要安排大家的顺序。我们保证一定有解。
    Input
    第一行一个整数T(1 <= T <= 5),表示测试数据的个数。
    然后对于每个测试数据,第一行有两个整数n(1 <= n <= 30000)和m(1 <= m <= 100000),分别表示人数和约束的个数。
    然后m行,每行两个整数a和b,表示有一个约束a号必须在b号之前。a和b必然不同。
    Output
    对每个测试数据,输出一行排队的顺序,用空格隔开。
    这种题目需要使用优先队列比较方便
    AC代码:

    #include<cstdio>
    #include<cstring>
    #include<vector>
    #include<queue>
    using namespace std;
    
    int n,m,num;
    const int MAXN=30005;
    const int MAXM=100005;
    vector<int> G[MAXN];
    int ans[MAXN];
    int in[MAXN];
    
    void func()
    {
    	priority_queue<int> q;
    	int cnt=0;
    	while(!q.empty()) q.pop();
    	for(int i=1;i<=n;i++) if(!in[i]) q.push(i);
    	while(!q.empty())
    	{
    		int now=q.top();
    		q.pop();
    		ans[++cnt]=now;
    		for(int i=0;i<G[now].size();i++)
    		{
    			int v=G[now][i];
    			if(--in[v]==0) q.push(v);
    		}
    	}
    }
    int main()
    {
    	int T,u,v;
    	scanf("%d",&T);
    	while(T--)
    	{
    		scanf("%d%d",&n,&m);
    		for(int i=1;i<=n;i++) G[i].clear();
    		memset(ans,0,sizeof(ans));
    		memset(in,0,sizeof(in));
    		for(int i=0;i<m;i++)
    		{
    			scanf("%d%d",&u,&v);
    			G[v].push_back(u);
    			in[u]++;
    		}
    		func();
    		for(int i=n;i>=1;i--)
    		{
    			if(i!=1) printf("%d ",ans[i]);
    			else printf("%d
    ",ans[i]);
    		}
    	}
    	return 0;
     } 
    

    大体就是这样,在实际操作中还需要根据情况进行各种变通,但只要深入了解拓扑排序的特点应该都能迎刃而解
    (未完待更)

     

  • 相关阅读:
    Sqlserver根据条件去除重复数据并且留下的是最大值数据
    C# Linq及Lamda表达式实战应用之 GroupBy 分组统计
    MVVM模式WPF的ComboBox数据绑定,使用Dictionary作为数据源
    C# System.Timers.Timer定时器的使用和定时自动清理内存应用
    SQL优化策略
    只要不放弃,总有出头之路
    2 Python基础
    4 动态库和静态库
    1 VS常用快捷键
    2 C语言环境、编译
  • 原文地址:https://www.cnblogs.com/qgmzbry/p/10662104.html
Copyright © 2011-2022 走看看