拓扑排序
简介
- 将有向图中的顶点以线性的方式排序,在任何连接自顶点u到v的有向边uv,在最后排序的结果中u总在v之前。(有向无环图)
思想
- 从有向图中选取一个入度为0的顶点,输出。
- 删除该顶点,包括关联的所有边。
- 直到图空或者找不到入度为0的点。
- 每次的选择只能是唯一或者不确定,不能有矛盾的情况。
模板
#include <stdio.h>
#include <set>
#include <vector>
#include <algorithm>
#include <cstring>
#include <iostream>
#define maxn 30010
using namespace std;
int n,m;
int indegree[ maxn ];
vector< vector<int> > G(maxn); //存储关系
set<int> s;
int ans[maxn]; //最终的顺序
void init(int n)
{
memset(indegree,0,sizeof(0));
s.clear();
for(int i = 1; i <= n ; i++)
G[i].clear();
memset(ans,0,sizeof(ans));
}
int toposort()
{
int i,pos=0;
//按照题目应该先加入最大的
for(i = n; i >= 1 ;i--)
if(indegree[ i ] == 0)
s.insert( i );
while ( !s.empty() )
{
i = *s.begin();
ans[++pos]=i;
s.erase( s.begin() );
//删除掉这个点,并且把所有连接边删除,其他点的入度也要改变
while ( !G[ i ].empty() )
{
//G[i]的出边的点的入度减1
indegree[G[i].back()] --;
//入度为0了 加入set
if(indegree[G[i].back()] == 0)
s.insert(G[i].back());
G[i].pop_back();
}
}
if (pos==n)
return 1;
else
return 0;
}
int main()
{
int T,i;
scanf( "%d", &T );
while ( T-- )
{
scanf("%d%d",&n,&m);
init(n);
//读入关系
while(m--)
{
int i,j;
scanf("%d%d",&i, &j);
//不重复加载
if(find(G[i].begin(),G[i].end(),j) == G[i].end())
{
G[i].push_back(j);
indegree[j]++;
}
}
//拓扑排序
toposort();
for (i=1;i<=n;i++)
printf("%d ",ans[i]);
printf("\n");
}
return 0;
}
例题(逆向建图+拓扑排序)
思路:
如果按照普通的拓扑排序,正向建图,每次选取入度为0的最小的点(小根堆),那么看似可以,但是题目要我们尽量把题号最小的放到前面输出。那么也就是说
5->4->1
和3->2
,我们也应该是输出5->4->1
后,再考虑2和3,因为1的优先级太高了,我们想尽可能快得输出1,所以就要把1之前的统统先输出。那么就不能按照通常的方法。我们考虑逆向建图,那么在有多个入度为0的顶点时,我们选择数字大的点。为什么?假设现在是3和5的入度为0,那我们肯定想的是,最终结果中3要最先输出,那么我们现在就要把5放到3后面输出,因为后面我们会逆序输出,所以现在就让5加入大根堆,同时删除与5关联的边。那么这样每次选择大的,放到前面,那么之后逆序肯定是在后面。
AC代码:
#include <stdio.h>
#include <set>
#include <vector>
#include <algorithm>
#include <cstring>
#include <iostream>
#define maxn 30010
using namespace std;
int n,m;
int indegree[ maxn ];
vector< vector<int> > G(maxn); //存储关系
set<int,greater<int> > s; //按照题目应该是大根堆
int ans[maxn]; //最终的顺序
void init(int n)
{
memset(indegree,0,sizeof(0));
s.clear();
for(int i = 1; i <= n ; i++)
G[i].clear();
memset(ans,0,sizeof(ans));
}
int toposort()
{
int i,pos=0;
for(i = 1; i <= n ;i++)
if(indegree[ i ] == 0)
s.insert( i );
while ( !s.empty() )
{
i = *s.begin();
ans[++pos]=i;
s.erase( s.begin() );
//删除掉这个点,并且把所有连接边删除,其他点的入度也要改变
while ( !G[ i ].empty() )
{
//G[i]的出边的点的入度减1
indegree[G[i].back()] --;
//入度为0了 加入set
if(indegree[G[i].back()] == 0)
s.insert(G[i].back());
G[i].pop_back();
}
}
if (pos==n)
return 1;
else
return 0;
}
int main()
{
int T,i;
scanf( "%d", &T );
while ( T-- )
{
scanf("%d%d",&n,&m);
init(n);
//读入关系
while(m--)
{
int i,j;
scanf("%d%d",&i, &j);
//不重复加载 逆向建图
if(find(G[j].begin(),G[j].end(),i) == G[j].end())
{
G[j].push_back(i);
indegree[i]++;
}
}
//拓扑排序
toposort();
//逆向输出
for (i=n;i >= 1;i--)
{
if(i == 1)
printf("%d\n",ans[i]);
else
printf("%d ",ans[i]);
}
}
return 0;
}