拓扑排序算法复习总结:
拓扑排序是图论中较为简单的一个算法,思路较为直接。
简单而言,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。(贴自百度百科)
拓扑排序思路:
(1)找入度为0的结点存入答案序列;
(2)将与该结点相关的出边删除(相应的结点入度减1);
重复上述过程至不存在入度为0的结点。
拓扑排序一般用法:
(1)拓扑排序通常涉及图中结点的优先级关系,要由一系列结点之间的优先关系给出一个适合的优先级序列可采用拓扑排序进行。
(2)判断有向图中是否存在环(也可采用dfs):利用拓扑排序算法进行至图中不存在入度为0的结点,而在一个环内所有结点的入度都不能为0,这样就可通过判断拓扑序列的长度是否为结点总数来判断是否存在环。
输出拓扑序列:
HDU1285:输出拓扑序列,要求字典序最小。
如果不要求字典序最小,用任意一种数据结构来存储待得的元素均可,而这里要求字典序最小,故采用优先队列存储。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
#define maxn 30005
priority_queue< int,vector<int>,greater<int> >q;
vector<int>mp[maxn];
vector<int>topo_ans;
int in[maxn];
void topo(int n)
{
int i,j;
for(i=1;i<=n;i++)
{
if(in[i]==0)
{
q.push(i);
}
}
while(!q.empty())
{
int now=q.top();
q.pop();
topo_ans.push_back(now);
for(i=0;i<mp[now].size();i++)
{
in[mp[now][i]]--;
if(in[mp[now][i]]==0)
{
q.push(mp[now][i]);
}
}
}
}
int main()
{
int i,j;
int n,m;
while(scanf("%d%d",&n,&m)!=EOF)
{
while(!q.empty()) q.pop();
for(i=0;i<=n;i++)
{
mp[i].clear();
}
topo_ans.clear();
memset(in,0,sizeof(in));
int x,y;
for(i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
mp[x].push_back(y);
in[y]++;
}
topo(n);
for(i=0;i<topo_ans.size();i++)
{
if(i==topo_ans.size()-1)
{
printf("%d\n",topo_ans[i]);
}
else
{
printf("%d ",topo_ans[i]);
}
}
}
return 0;
}
判断有向图是否存在环:
HDU3342:说了一堆,意思就是判断是否有环。
判断得到的拓扑序列长度是否等于结点总数即可。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
#define maxn 150
queue<int>q;
vector<int>mp[maxn];
int in[maxn];
int len;
void topo(int n)
{
int i,j;
for(i=0;i<=n-1;i++)
{
if(in[i]==0)
{
q.push(i);
}
}
while(!q.empty())
{
int now=q.front();
q.pop();
len++;
for(i=0;i<mp[now].size();i++)
{
int x=mp[now][i];
in[x]--;
if(in[x]==0)
{
q.push(x);
}
}
}
}
inline void ini(int n)
{
while(!q.empty())
{
q.pop();
}
for(int i=0;i<=n;i++)
{
mp[i].clear();
in[i]=0;
}
len=0;
}
int main()
{
int i,j;
int n,m;
while(scanf("%d%d",&n,&m)!=EOF)
{
if(n==0)
{
break;
}
ini(n);
int x,y;
for(i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
mp[x].push_back(y);
in[y]++;
}
topo(n);
if(len!=n)
{
printf("NO\n");
}
else
{
printf("YES\n");
}
}
return 0;
}
反向拓扑:
HDU4587:要求1在前,1相同的基础上2在前,1.2相同的基础上3在前…这与字典序最小不一样了。假设有两条拓扑序列:6,3,1,4,5,2和2,4,5,6,1,3,这时前者字典序大于后者,但是前者才是答案。因为我们从序列头到尾获取字典序最小其实是每次贪心选择小的结点,但这样选择不一定是最优解,因为前面的结点编号小可能会导致没有先选择1这样的小结点所在的路径。这时采用逆向思维,反向建图,先输出结点序号大的,再逆序输出结果即可,因为这样相当于每次都先选择了最大的元素放在尾部,而放在尾部无后效性,故序号小的一定在前面。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
#define maxn 30050
priority_queue<int>q;
vector<int>mp[maxn];
vector<int>topo_ans;
int in[maxn];
void topo(int n)
{
int i,j;
for(i=1;i<=n;i++)
{
if(in[i]==0)
{
q.push(i);
}
}
while(!q.empty())
{
int now=q.top();
q.pop();
topo_ans.push_back(now);
for(i=0;i<mp[now].size();i++)
{
int x=mp[now][i];
in[x]--;
if(in[x]==0)
{
q.push(x);
}
}
}
}
inline void ini(int n)
{
while(!q.empty())
{
q.pop();
}
for(int i=0;i<=n;i++)
{
mp[i].clear();
in[i]=0;
}
topo_ans.clear();
}
int main()
{
int i,j;
int n,m;
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
ini(n);
int x,y;
for(i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
mp[y].push_back(x);
in[x]++;
}
topo(n);
for(i=topo_ans.size()-1;i>=0;i--)
{
if(i!=0)
{
printf("%d ",topo_ans[i]);
}
else
{
printf("%d\n",topo_ans[i]);
}
}
}
return 0;
}