top排序的定义

对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边<u,v>∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。

Java自动生成的拓扑图位置信息_i++


那么如何实现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
*/