拓扑排序

拓扑序列:

定义:将图中顶点以线性方式进行排序,对于任何使得从顶点u到顶点v的有向边,顶点u总是排在顶点v的前边,这样得到的序列叫拓扑序列。

显然能瞒住拓扑序列的图都是有向无环图简称DAG

所有DAG存在一个或多个拓扑序列。

图例:

DAG

这是一个有向无环图(DAG),我们可以根据拓扑序列定义得到几组拓扑序列

1、3、2、5、4、6、7
3、1、5、2、4、7、6

为什么会出现多组拓扑序列呢?

我们观察上图可以发现1、3这两个结点没有任何关系,不存在指向关系,所以他们两个是并列关系,我们可以将1放在前,也可以将3放在前面,同理(6、7),(2、5),(2、4)(4、5)这两对结点也是满足任意先后顺序!

拓扑排序

定义:由AOV网构造拓扑序列的过程叫做拓扑排序。

AOV网是一个有向无环图。

拓扑排序方法

一、Kahn(卡恩算法)
二、dfs算法

卡恩算法

步骤:

1、把入度为0的点入队列
2、把队首元素出队并输出,从图中删除此点以及它的边
3、重复上述两步直到不存在入度为0的点

图例:(以上图DAG图为例)
在这里插入图片描述
最后得到的拓扑序列

1、3、2、5、4、7、6

代码实现:

void topsort()
{
    queue<int> q;
    //先找出入度为0的点,入队
    for(int i=1; i<=n; i++) 
    if(ru[i]==0)
        {
            q.push(i);
            ts[++tot]=i;//拓扑序列记录在ts数组
        }

    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        //将度清0
        for(int i=head[u]; i; i=edge[i].next)
        {
            int v=edge[i].to;
            ru[v]--;
            //入度为0则push进队列
            if(ru[v]==0)
            {
                q.push(v);
                ts[++tot]=v;//记录在ts数组中
            }
        }
    }
}

卡恩算法复杂度分析

由于我们要入队n个元素,以及删除所有的边,所以复杂度是O(V+E)

一道例题(需要一点dp知识)
旅行计划洛谷

AC代码:

#include <bits/stdc++.h>

using namespace std;
const int maxn=1e5+10;
//ts存放拓扑序列,head存放u相关的边的最后条边的编号,dp存放最优解
int ts[maxn],head[maxn],dp[maxn],ru[maxn];//ru存放结点入度
int n,m,ecnt,tot;//ecnt存放边数
struct edge
{
    int to,next;//边的指向结点to,next下一条边的编号
} edge[maxn<<1];

void add(int u,int v)
{
    edge[++ecnt].to=v;
    edge[ecnt].next=head[u];//指向上一条边
    head[u]=ecnt;//下一条边该指向当前边
}

void topsort()
{
    queue<int> q;
    for(int i=1; i<=n; i++) if(ru[i]==0)
        {
            q.push(i);
            ts[++tot]=i;
        }

    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        //将度清0
        for(int i=head[u]; i; i=edge[i].next)
        {
            int v=edge[i].to;
            ru[v]--;
            if(ru[v]==0)
            {
                q.push(v);
                ts[++tot]=v;
            }
        }
    }
}

int main()
{

    scanf("%d%d",&n,&m);
    //作图
    for(int i=1; i<=m; i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        add(u,v);
        ru[v]++;//v的入度+1
    }
    topsort();
    //动态规划
    fill(dp+1,dp+n,1);
    //最外层遍历拓扑排序的后的前n个结点
    for(int i=1; i<=n; i++)
    {
        int u=ts[i];
        //遍历所有相关联的边
        for(int j=head[u]; j; j=edge[j].next)
        {
            int v=edge[j].to;
            dp[v]=max(dp[v],dp[u]+1);//动态转移方程
        }
    }
    for(int i=1; i<=n; i++)
    {
        printf("%d\n",dp[i]);
    }

    return 0;
}

DFS解决拓扑排序

利用dfs从一个结点一直往下搜索,一直标记直到一个结点的出度为0,也就是这个结点没有边可以遍历,就停止遍历,记录在拓扑序列数组上然后返回继续dfs。最后我们倒叙输出得到的拓扑序列即可!

dfs状态树图解

这颗dfs状态树是,最开始的DAG图,从3号结点出发的状态树

在这里插入图片描述

同理然后返回4号结点,在遍历7号结点标记7,7记录在拓扑序列中,最后返回4,记录4进入拓扑序列**(4的状态树遍历完成所以4进入)**,返回2,进入2号结点指向的7,由于7号已经标记不能再遍历以此类推…我们通过3号状态树可以得到拓扑序列6、7、4、2、5、3,在dfs(1)得到最终的拓扑序列6、7、4、2、5、3、1,我们在逆序得到真正的拓扑序列1、3、5、2、4、7、6。

代码实现:(这是用dfs得到拓扑序列解决上一道例题)

#include <bits/stdc++.h>

using namespace std;
const int maxn=1e5+10;
int head[maxn],dp[maxn],ts[maxn],vis[maxn];
int sum,tot;
int n,m,u,v;
struct edge{
   int to,next;
}edge[maxn<<1];

void add(int u,int v)
{
    edge[++sum].to=v;
    edge[sum].next=head[u];
    head[u]=sum;
}
//dfs
void dfs(int u)
{
    //遍历所有的边
    for(int i=head[u];i;i=edge[i].next)
    {
        v=edge[i].to;
        if(!vis[v])
        {
            vis[v]=1;//标记
            dfs(v);
        }
    }
    ts[++tot]=u;//记录拓扑序列
}

int main(){

     scanf("%d%d",&n,&m);
     fill(dp+1,dp+1+n,1);//初始化dp数组将其中所有值变为1
     for(int i=1;i<=m;i++)
     {
         scanf("%d%d",&u,&v);
         add(u,v);
     }
     for(int i=1;i<=n;i++)
     {
         if(!vis[i])
         {
             vis[i]=1;
             dfs(i);
         }
     }
     //dp
     for(int i=n;i>=1;i--)
     {
          int u=ts[i];
          for(int j=head[u];j;j=edge[j].next)
          {
              int v=edge[j].to;
              dp[v]=max(dp[v],dp[u]+1);
          }
     }
     for(int i=1;i<=n;i++)
     {
         printf("%d\n",dp[i]);
     }

    return 0;
}

dfs复杂度分析

由于要遍历V个结点以及E条边所以为O(V+E)与卡恩算法相同。
感谢观看,如果对你有帮助,多多点赞支持!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值