【纯代码向】 运用优先队列对拓补排序的实现

在这篇文章里,我想贴出我的拓补排序代码与分析,讲一讲它是如何从最慢的方法(O(n方))变到理论上最优的方法(O(n))的。

分析

 首先,我们需要知道我们面对什么样的问题:我们需要每次找出出度为零(倒序)或者入度为零(正序)的节点,删除这个点后,将这个节点与“父结点”相连的边删除,重复该步骤直到无点可删。
 那么第一种方法就出来了。

方案一 逐次检索

每一次我们删除一个点,就去寻找一次图上是否有出度为零的点,这样时间为n方,会浪费大量时间在不必要的搜索上。但相对比较好写,故此不贴出代码,不提倡。

方案二 检查,入队

请大家思考一些问题:有什么点,它原本的出度不为0,然而经过了一次删边之后,出度就变化为0了呢?答案是,那些跟删除的那些点相连,出度为1的点。因为每次删除只会影响到这些点,其他店的出度是不变的,所以我们只需在这中间找即可。

类似于广搜,我们用一个队列(stack[xx])储存出度为零的点。

    int stack[20005],be[20005];//be[xx]储存点的出度
    vector <int> son[20005];//vector 作为邻接表
    int p1=0,p2=0;
    for(int i=1;i<=n;i++)
    if(be[i]==0) stack[++p2]=i;
    while(++p1<=p2){
        int h=stack[p1];
        for(int i=son[h].size()-1;i>=0;i--)
        {
            int k=son[h][i];
            if(--be[k]==0) stack[++p2]=k;
        }
    }

不难看出,因为每次操作,我们都可以删去一条边,所以这个算法的时间复杂度是O(m)的,但仍不是我们理想的结果,因为每次都要进行一次判断,常数可能很大。
接下来,就是我们的最终解法。

方案三 优先队列

优先队列真是太好用了!感觉这东西后面会用的很多,而的确是这样,所以请确保你能独立打出优先队列的模板(可以去看我的前一篇博客,)。

数据结构

我们需要这几个东西:
- 出度最小的点队列(有序)——优先队列 因为我们需要优化,所以不能在每次循环后去寻找入度为零的点,而是应当排序。注意,这里的优先队列也得使用结构体,因为我们要记录原先这个点的位置。
- 每个节点的出度
- 每个点的父结点
- 点的访问情况 **运用优先队列,会导致一个点重复进出队列,为了不重复,我们需要用一个数组来判断是否访问过。

using namespace std;
struct node{
    int du,pos;
    bool operator < (const node& x)const{
        if(du>x.du) return true;
        return false;
    }
};
priority_queue<node> Q;

这里是结构体里的东西,注意重载运算符,这是优先队列的前提。

void readit()
{
    memset(vis,0,sizeof(vis));
    int n,m;scanf("%d%d",&n,&m);
    for(int a,b,i=1;i<=m;i++){
        scanf("%d%d",&a,&b);
        fa[b].push_back(a);
        chu[a]++;
    }
    for(int i=1;i<=n;i++)
      Q.push((node){chu[i],i});
}

读入数据的过程,我用vector fa[n]来存储父结点。

while(!Q.empty()){
        node x=Q.top();Q.pop();
        int k=x.pos;
        if(vis[k]) continue;
        if(x.du!=0) break;
        vis[k]=true;
        //doit(k);         放你想做的事吧
        for(int i=fa[k].size()-1;i>=0;i--)
        {
            int p=fa[k][i];
            chu[p]--;
            Q.push((node){chu[p],p});
        }
    }

这是主程序的部分。注意的点有两个:首先,要以优先队列非空作为判断的依据,这是为了避免运行错误的基准条件,任何数据结构一定要判断是否超了下界。
其次,是判断这个点的度是否为零:如果不为零,而因为这又是度最小的那一个,那么说明该图有环,情况错误,退出。剩下的是一些无关紧要的东西了,都可以在我之前的文章里找到。

尾声

这一点内容也结束了,虽然不多,但感觉写起来很累。效率是否过低?我开始理解为什么很多大大们分析完思路就直接贴代码了。下一次我会酌情改进,那么再见,你的支持是我的动力(之一),莫忘评论指出我的不足,谢谢!
2017.10.24

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值