图的拓扑排序

首先我们应该明白,图的拓扑排序是对有向无环图来说的的,无向图和有环的有向图没有拓扑排序,或者说不存在拓扑排序。对于一个有向无环图G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若图G存在边< u,v >,则u在线性序列中出现在v之前。对有向图进行拓扑排序产生的线性序列称为满足拓扑次序的序列,简称拓扑序列。一个有向无环图通常可以表示某种动作序列或方案,而有向无环图的拓扑排序通常表示某种方案切实可行。

以下图为例


v1v2v3v4v5v6v7v8是有向无环图的一个拓扑排序,与此同时,v1v3v2v4v5v6v7v8也是它的一个拓扑排序,也就是说,对于一个有向无环图,拓扑序列可能不止一个。对于上图来说,其拓扑序列也远远不止上面的两个。继续查看拓扑序列,可以发现图中所有的有向边的起点都在终点的左边,扩展开来,若在有向图G中从顶点vi到顶点vj有一条路径,则在拓扑序列中顶点vi必在顶点vj之前。

下面我们介绍一种无前驱的顶点优先的拓扑排序算法。

1.基本算法

无前驱的顶点优先的拓扑排序算法就是每一步总是输出当前无前驱(即入度为零)的顶点。

算法有以下三步:

(1)从有向图中选择一个没有前驱(即入度为0)的顶点并输出。

(2)从网中删除该顶点,并删除从该顶点出发的全部的有向边。

(3)重复上述步骤,直到剩余网中不再存在没有前驱的顶点为止。

对于上面的算法,如果最终存在不能剩余的点,则剩余的点和其间的边一定构成有环路,否则算法结束时图G中的点都会被删除并输出的。

在实现上,可以使用链式前向星来存储整张图,再开一个额外的数组存储每个点的入度,每删除一个点,就遍历以这个点为起点的边,将边对应的终点的入度减1即可选择并删除下一点。在如何选择下一点的策略上,如果点的数目较少,可以遍历入度数组,选择第一个没有被删除的且入度为0的点。也可以用一个队列存储当前已经发现的入度为0的点, 在更新入度的同时更新这个队列。那么如果只是用一个变量来控制队列的头位置而不是真正的删除队头数据(实际上大多时候都是这么做的),那么队列最终存储的就会是一个完整的拓扑排序序列。

2.具体实现

如果使用链式前向星存储图,在数据输入的时候同时统计每个点的入度,并存入ind数组中。

代码实现:

void Top_sort()
{
    int queue[maxn];
    int tmp=0;
    //先将图中入度为0的顶点加入队列
    for(int i=1;i<=n;i++)
      if(ind[i]==0)
        queue[tmp++]=i;
    //使用队列中的点更新ind数组并生成拓扑排序序列
    for(int i=0;i<tmp;i++)
      for(int k=head[queue[i]];k!=-1;k=map[k].next)
      {
          ind[map[k].to]--;
          if(ind[map[k].to]==0)//如果ind数组变为0,说明新的没有前驱的顶点被找到,加入队列中
            queue[tmp++]=map[k].to;
      }
    //输出拓扑排序序列
    for(int i=0;i<tmp;i++)
      cout<<queue[i]<<endl;
}

对于上面的算法,如果tmp的值小于图中的顶点数n,则说明拓扑排序不存在。该算法在O(m)的时间内对ind数组进行初始化,在0(n)的时间内对queue进行初始化,后面的部分虽然是两成循环,但实际上是遍历了每条边,所以时间复杂度应该是O(m)的,总的时间复杂度是O(n+m)。

链式前向星的完整代码如下:(代码中加入s[]数组来确定si是否存在)

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
#define MAXN 510
typedef struct node
{
    int to;
    int next;
}map;
int head[MAXN*MAXN];
int ind[MAXN];
int queue[MAXN];
bool s[MAXN];
map edge[MAXN*MAXN];
void Top_sort(int n)
{
    int cnt=0;
    for(int i=1;i<=n;i++)
      if(ind[i]==0&&s[i])
        queue[cnt++]=i;
    for(int i=0;i<cnt;i++)
      for(int k=head[queue[i]];k!=-1;k=edge[k].next)
      {
          ind[edge[k].to]--;
          if(ind[edge[k].to]==0)
            queue[cnt++]=edge[k].to;
      }
    for(int i=0;i<cnt;i++)
      if(i==cnt-1)  printf("%d\n",queue[i]);
      else printf("%d ",queue[i]);
}
int main()
{
    int n,m,a,b;
    while(scanf("%d%d",&n,&m)!=-1)
    {
        memset(ind,0,sizeof(ind));
        memset(head,-1,sizeof(head));
        memset(s,false,sizeof(s));
        int tmp=0;
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d",&a,&b);
            s[a]=s[b]=true;
            ind[b]++;
            edge[tmp].to=b;
            edge[tmp].next=head[a];
            head[a]=tmp++;
        }
        Top_sort(n);
    }
    return 0;
}


前向星的完整代码:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
#define MAXN 510
typedef struct node
{
    int from;
    int to;
}map;
int head[MAXN*MAXN];
int ind[MAXN];
int queue[MAXN];
bool s[MAXN];
map edge[MAXN*MAXN];
int cmp(const map &a,const map &b)
{
    if(a.from==b.from)
      return a.to<b.to;
    return a.from<b.from;
}
void Top_sort(int n,int m)
{
    int cnt=0;
    for(int i=1;i<=n;i++)
      if(ind[i]==0&&s[i])
        queue[cnt++]=i;
    for(int i=0;i<cnt;i++)
      for(int k=head[queue[i]];edge[k].from==queue[i]&&k<=m;k++)
      {
          ind[edge[k].to]--;
          if(ind[edge[k].to]==0)
            queue[cnt++]=edge[k].to;
      }
    for(int i=0;i<cnt;i++)
      if(i==cnt-1)  printf("%d\n",queue[i]);
      else printf("%d ",queue[i]);
}
int main()
{
    int n,m,a,b;
    while(scanf("%d%d",&n,&m)!=-1)
    {
        memset(ind,0,sizeof(ind));
        memset(head,-1,sizeof(head));
        memset(s,false,sizeof(s));
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d",&a,&b);
            s[a]=s[b]=true;
            ind[b]++;
            edge[i].from=a;
            edge[i].to=b;
        }
        sort(edge+1,edge+1+m,cmp);
        for(int i=1;i<=m;i++)
          if(edge[i].from!=edge[i-1].from)
            head[edge[i].from]=i;
        Top_sort(n,m);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值