top排序的定义
对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边<u,v>∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。
那么如何实现top排序呢?
(1) BFS用队列实现
一般我们根据排序的特点可以发现,从某一个入度为0的节点出发,可以到达他的子节点,子节点相应的入度减一,如果这个时候子节点的入度减小至0了,那么就可以将该节点将转化成一个可排序节点,可以访问,从该节点起又能访问他的子节点,重复这个过程直到所有的节点都能被访问。
算法过程如下:
void top()
{
int v,u;
1.计算每一个节点的入度
for(int i=0;i<n;i++)//计算每一个节点的入度
{
for(int j=0;j<G[i].size();j++)
{
v=G[i][j];//取出子节点
count[v]++;//计算入度
}
}
2.将入度为0的节点加入队列
for(int i=0;i<n;i++)
{
if(count[i]==0) que.push(i);//加入入度为0的节点
}
3.当队列非空时,访问节点的子节点,子节点相应的入度减一
while(!que.empty())//非空
{
u=que.front();
que.pop();//取出队头元素
cout<<u;//输出复合条件入度为0的节点
for(int i=0;i<G[u].size();i++)
{
v=G[u][i];
count[v]--;//可到达入度减一
if(count[v]==0) que.push(v);//入度为空则入队
}
}
bool ans=0;
for(int i=0;i<n;i++) if(count[i]!=0) ans=1;//表示有环存在top排序无效
}
1.可以看到算法的时间复杂度是O(V+E).
具体实现如下:
#include <iostream>
#include <queue>
#include <stack>
using namespace std;
const int maxn=10010;
int n,m;//点数和边数
vector<int> G[maxn];//图
int count[maxn]={0};//每个节点的入度
vector<int>to;
queue<int>que;
void top()
{
int v,u;
for(int i=0;i<n;i++)//计算每一个节点的入度
{
for(int j=0;j<G[i].size();j++)
{
v=G[i][j];//取出子节点
count[v]++;//计算入度
}
}
for(int i=0;i<n;i++)
{
if(count[i]==0) que.push(i);//加入入度为0的节点
}
while(!que.empty())//非空
{
u=que.front();
que.pop();//取出队头元素
to.push_back(u);//加入答案序列
for(int i=0;i<G[u].size();i++)
{
v=G[u][i];
count[v]--;
if(count[v]==0) que.push(v);//入度为空则入队
}
}
bool ans=0;//表示无环路
for(int i=0;i<n;i++) if(count[i]!=0) ans=1;
if(!ans)
{
for(int i=0;i<to.size();i++)
{
cout<<to[i]<<' ';
}
}
}
int main()
{
scanf("%d%d",&n,&m);
int u,v;
for(int i=0;i<m;i++)
{
scanf("%d%d",&u,&v);
G[u].push_back(v);//有向图
}
top();
return 0;
}
/*
6 8
1 2
1 4
2 3
2 5
3 6
4 5
5 1
5 6
*/
(2) DFS深度搜索
我们发现,父节点是子节点的先决条件,由父节点访问完后才能判断是否能继续访问子节点,那么,从起点开始顺着邻接关系往下找,可以一直找到边界节点,然后返回父节点,试想用递归实现这个过程,在访问时不做处理,当递归返回时输出这个节点,那么这不就是一个逆序的拓扑排序吗?基于这个思想,很容易想到递归算法:
void top(int u)
{
visit[u]=1;//标记为访问
for(int i=0;i<G[u].size();i++)//计算每一个节点的入度
{
int v=G[u][i];//子节点
if(!visit[v])//子节点未访问
{
top(v);//递归访问子节点
}
}
to.push_back(u);
}
可以看到递归的复杂度是O(V+E), DFS深度搜索反向输出虽然很方便,但是存在一个问题那就是如果图中存在环路,那么DFS仍然会得到一个top序列,显然这个序列是无效的
具体实现如下:
#include <iostream>
#include <vector>
using namespace std;
const int maxn=10010;
int n,m;//点数和边数
vector<int> G[maxn];//图
bool visit[maxn]={0};//标记节点是否访问
vector<int>to;
void top(int u)
{
visit[u]=1;//标记为访问
for(int i=0;i<G[u].size();i++)//计算每一个节点的入度
{
int v=G[u][i];//子节点
if(!visit[v])//子节点未访问
{
top(v);//递归访问子节点
}
}
to.push_back(u);
}
int main()
{
scanf("%d%d",&n,&m);
int u,v;
for(int i=0;i<m;i++)
{
scanf("%d%d",&u,&v);
G[u].push_back(v);//有向图
}
for(int i=0;i<n;i++)
{
if(!visit[i]) top(i);
}
for(int i=to.size()-1;i>=0;i--) cout<<to[i]<<' ';
return 0;
}
/*
4 4
0 1
1 2
0 2
2 3
*/