拓扑排序

发布时间 2023-07-28 10:28:13作者: liuwansi

拓扑排序

给定一张有向无环图,排出所有顶点的一个序列A满足:
对于图中的每条有向边(x,y)x在A中的出现都在y之前,则称A是改图的顶点的一个拓扑序。

如图所示,{2 3 5 1 7 4 6},{3 2 1 5 7 6 4}都是合法的拓扑序。

用途:

可以判断有向图中是否有环,可以生成拓扑排序

Kahn算法实现拓扑排序

e[x]存点x的邻点,tp存拓朴序列,din[x]存点x的入度。

算法的核心:

用队列维护一个入度为0的节点的集合。

  • 1.初始化,队列q压入所有入度为0的点的集合.(起始步)
  • 2.每次从q中取出一个点x放入数组tp[]当中
  • 3.然后将x的所有出边删除。若将边(x,y)删除后,y的入度变为0,则将y也压入q中;
  • 4.不断重复2,3过程,直到队列q为空。
  • 5.若tp[]中元素的个数等于n,则有拓扑序;否则,有环.

代码模板如下所示:

vector<int> e[N],tp;
int din[N];

bool topsort()
{
    queue<int>q;//维护入度为零的点
    for(int i=1;i<=n;i++)
    if(din[i]==0)q.push(i);
    while(q.size())//只要队不空,就从队头取出一个元素弹出
    {
        int x=q.front();
        q.pop();
        tp.push_back(x);//将取出的元素压入tp数组
        for(auto y:e[x])
        {
            if(--din[y]==0)q.push(y);//判断每次减一的时候入度有没有变为0
        }
    }
    return tp.size()==n;
}
int main()
{
    cin>>n>>m;
    for(int i=0;i<m;i++)
    {
        cin>>a>>b;
        e[a].push_back(b);//压入a的临点b
        din[b]++;//同时b的入度要加 1
    }
    if(!topsort())puts("-1");//有环无解
    else for(auto x:tp)printf("%d ",x);//迭代循环输出
    return 0;
}

DFS实现拓扑排序

e[x]存x的临点,tp存拓扑序列,c[x]存点的颜色
算法的核心是变色法,一路搜索一路给点进行变色,如果有拓扑排序,每个点的颜色都会从0 -> -1 ->1,经历3次变色

  • 1.初始状态所有的状态都染色为0;
  • 2.枚举每个点,进入x点,就把x染色为-1.然后,枚举x的儿子节点y,如果y的颜色为0,说明没碰过该点,进入y继续走
  • 3.如果枚举完x的儿子,没发现环,则x染色为1,并把x压入tp数组
  • 4.如果发现有个熊孩子的颜色为-1,说明回到了祖先节点,发现了环,则一路返回false,退出程序.

代码模板如下所示:

vector<int> e[N],tp;
int c[N];//染色数组

bool dfs(int x)
{
    c[x]=-1;
    for(int y:e[X])
    {
        if(c[y]<0)return 0;//有环
        
        else if(!c[y])
        if(!dfs(y))return 0;
    }
    c[x]=1;
    tp.push_back(x);
    return 1;
}

bool topsort()
{
    memset(C,0,sizeof(c));
    for(int x=1;x<=n;x++)
    {
        if(!c[x])
            if(!dfs(x))return 0;
    }
    reverse(tp.begin(),tp.end());
    return 1;
}

总结

  • Kahn算法 队列维护,顺着拓扑收集点
  • dfs算法,系统栈维护,逆着拓扑序收集点
  • 时间复杂度:O(E+V)
  • 不连通:不影响拓扑排序
  • 求字典序的最小拓扑序,将Kahn算法中的队列替换为优先队列(小根堆)
    时间复杂度为O(E+VlogV)