拓扑排序的简单实现 (DFS && BFS && 静态邻接表)

果然还是码力不足啊……上午学长讲了一发静态邻接表的拓扑排序的BFS实现,感觉贼鸡儿简单,贼鸡儿明白,然而下午还是一条咸鱼打不出……刷了一遍算法导论看了看伪码差不多有点儿数了,话说算法导论上只有DFS实现心好累,说好的“瑞典式自助餐”呢【手动滑稽】?下面上一发拓扑排序的DFS实现,DFS实现好像很难按字典序排列,想了半天翻了无数博客也没找到,若有大神想到了如何在使用DFS的时候按字典序输出烦请评论告知,万分感谢!!

DFS实现的话是比较原理简单的,主要是利用了深度优先搜索的括号化原理,每一个节点都有一个隐形的时间戳,存在时间较短的并且存在时间区间被另一个节点的存在时间区间完全包括的话,另一个节点一定是此节点的父亲节点。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;
const int MAXN = 550;
struct node{
    int v, next;
}input[MAXN];
int head[MAXN], cnt, vis[MAXN];
vector<int> topo;

void add(int u, int v){
    input[++cnt].v = v;
    input[cnt].next = head[u];
    head[u] = cnt;  
}  //这里相当于是链表的头插入,使用cnt来表示节点的储存位置

void DFS(int i){
    vis[i] = -1;  //-1代表灰色节点,即正在探索中的节点
    for(int k = head[i]; k != 0;k = input[k].next){
        if(vis[input[k].v] == -1 ){  //如果探索到了正在探索中的节点,说明存在后向边,即存在环,无法完成拓扑排序
            printf("Backward Edge\n");
            return;
        }
        else if(vis[input[k].v] == 0){
            DFS(input[k].v);
        }
    }
    vis[i] = 1;  //1代表黑色节点,即探索完毕的节点
    topo.push_back(i);  //用来保存答案
}
void toposort(int n){
    memset(vis, 0, sizeof(vis));
    for(int i = 1;i <= n;++i){
        if(vis[i] == 0)
            DFS(i);  //对每一个节点进行判断
    }
}
int main()
{
    //freopen("input.txt", "r", stdin);
    int n, m;
    while(~scanf("%d %d", &n, &m)){
        cnt = 0;
        memset(head, 0, sizeof(head));
        for(int i = 0;i < m;++i){
            int u, v;
            scanf("%d %d", &u, &v);
            add(u, v);
        }
        toposort(n);
        /*for(int i = 1;i <= n;++i){
            printf("%d",i);
            for(int j = head[i]; j != 0;j = input[j].next){
                printf("    %d",input[j].v);
            }
            printf("\n");
        }*/   //这是测试静态链表是否储存成功的代码

        for(int i = topo.size() - 1;i >= 0;--i)
            printf("%d%c", topo[i], (i==0?'\n':' '));
        topo.clear();
    }
    return 0;
}

  给一组算法导论上的测试数据,可以画一画图验证正确性:

4 3
           1 2
           2 3
           4 3
           9 9
           1 2
           1 8
           2 8
           2 3
           3 6
           4 3
           4 5
           5 6
           7 8

接下来就是利用BFS实现的,这样实现的原理主要是首先让所有入度为0的节点入队,再轮流出队,同时与出队节点相邻的所有节点入度减一,再判断有无入度为0 的节点,有的话再入队。BFS也可以判断有向图是否有环,需要做的不过是统计一下删除的节点,如果是有向无环图的话,每次删除入度为0的节点最后一定能删除掉所有的节点,所有如果最后队列为空,图中还有剩余的节点,说明剩余的节点构成了一个环。

BFS的话也很方便可以实现按字典序排列,只要用优先队列使得节点编号小的在堆顶就可以了,这样每次弹出0入度节点的时候都会优先弹出编号小的。

下面上一波C艹实现,这同时也是HDU 1285的AC代码:

#include <iostream>
#include <cstdio>
#include <vector>
#include <queue>
#include <functional>  //选C++不用这个头文件好像不行??什么狗屁原理??
#include <cstring>
using namespace std;
const int MAXN = 10000;
struct node{
    int v, next;
}edge[MAXN];  //同样是静态邻接表实现,这个东西用着还挺方便的
int head[MAXN], cnt, indegree[MAXN];
vector<int> ans;
void add(int u, int v){
    for(int i = head[u];i != 0;i = edge[i].next){
        if(edge[i].v == v)  //这里注意要判断重边,如果是重边直接不管了
            return;
    }
    edge[++cnt].v = v;
    edge[cnt].next = head[u];
    indegree[v]++;
    head[u] = cnt;
}

void toposort(int n){
    priority_queue<int , vector<int>, greater<int> > q;
    for(int i = 1;i <= n;++i){
        if(indegree[i] == 0)
            q.push(i);
    }
    while(!q.empty()){
        int temp = q.top();
        q.pop();
        ans.push_back(temp);
        for(int i = head[temp]; i != 0;i = edge[i].next){
            indegree[edge[i].v]--;
            if(indegree[edge[i].v] == 0)
                q.push(edge[i].v);
        }
    }
}
int main()
{
    //freopen("input.txt", "r", stdin);
    int n, m;
    while(~scanf("%d %d", &n, &m)){
        cnt = 0;  //这里注意一定要每次置0
        memset(indegree, 0, sizeof(indegree));
        memset(head, 0, sizeof(head));
        for(int i = 0;i < m;++i){
            int u, v;
            scanf("%d %d", &u, &v);
            add(u, v);
        }
        toposort(n);
        for(int i = 0;i < (int)ans.size();++i){
            printf("%d%c", ans[i], (i == (int)ans.size() - 1 ? '\n':' '));
        }
        ans.clear();
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值