拓扑排序

8 篇文章 0 订阅
3 篇文章 0 订阅

转载自:https://www.cnblogs.com/MrSaver/p/9994720.html

     认真阅读你就会发现拓扑排序是基于DFS的,只是加入了一个栈来保存结果

  我们首先知道拓扑排序结果是一个线性排列,这说明了一定存在两类点,一类是入度为0,一类是出度为0。(入度为0指的是只想它的边为0,出度指的是它不指向任何边)。

  下图演示了一个从0度点出发的一个DFS树:

  

  首先2节点的邻接顶点是1和3,由于我们是DFS,它就会一条路走下去,所以先走左边,即到达1号节点,然后1号节点的邻接顶点是4,所以接下来箭头指向4,4是一个出度为0的节点,它没有邻接顶点,所以不用再往下递归,把4直接保存到栈中。接着返回到1节点,把1压入栈中,然后返回到2节点,接着走右边这条路,到达3号节点,接着从3号节点的邻接顶点出发,但是都已经访问过了,所以返回3后,直接把3压入栈中,最后返回2,把2压入栈中。

  所以最后的结果就是2,3,1,4。

  最后我们思考一下这个思想的正确性?我们可以这样想,无论我们从哪个节点出发,只要他指向其他顶点,我们就需要先去处理那些顶点,所以这个节点一定是后于其他结点入栈(也就在拓扑排序中先于其他结点),那些DFS深入到最后的节点,已经不指向任何顶点,直接入栈(在拓扑序列中是最后的)。其实我们理解到这里就可以了。

这个思路有点问题,当入度为0的结点不止一个的时候会出错。下面介绍一种传统的方法.

附上链式前向星实现的代码:

#include <iostream>
#include<cstdio>
#include<bits/stdc++.h>

using namespace std;

const int maxn = 100;

int cnt = 0;
int head[maxn];
int vis[maxn];
struct node{
    int next;
    int to;
}edge[maxn];

void add(int u, int v)
{
    edge[cnt].to = v;
    edge[cnt].next = head[u];
    head[u] = cnt++;
}

stack<int>st;
void topoSort(int s)
{
    for(int i = head[s]; ~i; i = edge[i].next){
        int to = edge[i].to;
        if(!vis[to])
            topoSort(to);
    }

    st.push(s);
    vis[s] = 1;
}

int main()
{
    int n, m;       //n个点m条边
    scanf("%d%d", &n, &m);
    fill(head + 1, head + 1 + n, -1);
    fill(vis + 1, vis + 1 + n, 0);
    for(int i = 1; i <= m; i++){
        int u, v;
        scanf("%d%d", &u, &v);
        vis[v] = 1;//标记当前点入度不为0
        add(u, v);
    }

    int s = -1;

    //找入度为0的点
    for(int i = 1; i <= n; i++){
        if(!vis[i]){
            s = i;
        }
    }
    //注意重新初始化
    fill(vis + 1, vis + n + 1, 0);//标记当前点没被访问


    topoSort(s);
    while(!st.empty()){
        printf("%d", st.top());
        st.pop();
        if(st.size() != 0)
            printf("->");
    }

    return 0;
}

拓扑排序的常规方法就是:1.找到入度为0的结点,2.删除这个结点,删除与这个结点相连的边,3.重复1,2直到所有点入度都变成0

以下是链式前向星实现的代码:

#include<bits/stdc++.h>
using namespace std;

const int maxn = 1e5 + 10;

int head[maxn];
int ingreee[maxn];
int vis[maxn];
int cnt = 0;
struct node{
    int to;
    int next;
//    int vis;
}edge[maxn];

int n, m;

void add(int u, int v)
{
    edge[cnt].to = v;
    edge[cnt].next = head[u];
    head[u] = cnt++;
//    edge[cnt].vis = 0;//这条边还没被删除
}
queue<int>q;
void topoSort()
{
    int cnt = 0;
    while(cnt < n){
        int s = -1;
        for(int i = 1; i <= n; i++){
            if(ingreee[i] == 0 && vis[i] == 0){//找入度为0的点
                s = i;
                break;
            }
        }

        for(int i = head[s]; ~i; i = edge[i].next){
            int to = edge[i].to;
//            if(!edge[i].vis){//如果当前边没被删除,删除它,入度减1
                ingreee[to]--;
//                edge[i].vis = 1;
//            }
        }
        q.push(s);
        vis[s] = 1;
        cnt++;
    }
}

void slove()
{
//    scanf("%d%d", &n, &m);
    fill(head + 1, head + n + 1, -1);
    fill(ingreee + 1, ingreee + n + 1, 0);
    fill(vis + 1, vis + n + 1, 0);
    for(int i = 1; i <= m; i++){
        int u, v;
        scanf("%d%d", &u, &v);
        add(u, v);
        ingreee[v]++;
    }
    topoSort();
    while(!q.empty()){
        printf("%d", q.front());
        q.pop();
        if(q.size()>0)printf("->");
    }
    printf("\n");
}

int main()
{
    while(~scanf("%d%d", &n, &m)){
        slove();
    }

    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值