C++详细讲解图的拓扑排序

 更新时间:2022年05月30日 11:20:08   作者:quicklsleap  
拓扑排序(Topological Sorting)若一个由图中所有点构成的序列 A 满足:对于图中的每条边 (x,y),x 在 A 中都出现在 y 之前,则称 A 是该图的一个拓扑序列

一、前言

且该序列必须满足下面两个条件:

  1. 每个顶点出现且只出现一次。
  2. 若存在一条从顶点 x到顶点 y的路径,那么在序列中顶点 x 出现在顶点 y的前面。

拓扑排序只适用于 AOV网 (有向无环图)

若图中有环,则一定不存在拓扑序。

可以证明,一个有向无环图,一定存在一个拓扑序列。有向无环图,又被称为拓扑图。

入度: 即有多少条边指向自己这个节点。

出度: 即有多少条边从自己这个节点指出去。

二、算法流程

算法流程:

用队列来执行 ,初始化所有入度为0的顶点入队。

主要由以下两步循环执行,直到不存在入度为 0 的顶点为止

选择一个入度为 0 的顶点,并将它输出;

删除图中从顶点连出的所有边

循环结束

若输出的顶点数小于图中的顶点数,则表示该图存在回路,即无法拓扑排序,

否则,输出的就是拓扑序列 (不唯一)

模板如下:

1.数组模拟队列实现拓扑排序

bool topsort()
{
    int hh = 0, tt = -1;
    // in[i] 存储点i的入度
    for (int i = 1; i <= n; i ++ )// 将所有入度为0的点加入队列
        if (in[i]==0)
            top[ ++ tt] = i;
    while (hh <= tt)
    {
        int t = top[hh ++ ];//找到入度为0的队头
  //遍历一下以t为头节点的的单链表,给每一个结点都要减去1,并再次找到入度为0的点
        for (int i = h[t]; i != -1; i = ne[i])
        {
        // 遍历 t 点的出边
            int j = e[i];
            if (-- in[j] == 0)//将入度减1,如果 j 入度为0,加入队列当中
                top[ ++ tt] = j;
        }
    }
    // 如果所有点都入队了,说明存在拓扑序列;否则不存在拓扑序列。
    return tt == n - 1;
}

2.使用STL queue实现拓扑排序

bool topsort(){
    queue<int> q;
    int t;
    for(int i = 1;i <= n; ++i)// 将所有入度为0的点加入队列
        if(in[i] == 0) q.push(i);
    while(q.size()){
        t = q.front();//每次取出队列的首部
        top[cnt] = t;//加入到 拓扑序列中
        cnt ++; // 序列中的元素 ++
        q.pop();
        for(int i = h[t];i != -1; i = ne[i]){
            // 遍历 t 点的出边
            int j = e[i];
            in[j] --;// j 的入度 --
            if(in[j] == 0) q.push(j); //如果 j 入度为0,加入队列当中
        }
    }
    if(cnt < n) return 0;
    else return 1;
}

时间复杂度 O(n+m), n表示点数,m表示边数

三、有向图的拓扑排序

给定一个 n 个点 m 条边的有向图,点的编号是 1 到 n,图中可能存在重边和自环。

请输出任意一个该有向图的拓扑序列,如果拓扑序列不存在,则输出 −1。

思路

我们每次找到入读为0的点,然后把他插入到队列里,然后将这个点删除,这也就意味着这个点连接的下一个点(可能是多个)的入度就会减1。

这个时候,我们就进入了下一轮。

我们因为前面将一个点删除了,那么它指向的点的入度就会都减去1,所以,就会出现新的点的入度为0,这个点显然是因为它的入度小,所以它理所应当的排在拓扑序里面在第二位。当前面的一个点没有了被删除了,那它就要首当其冲了。

和图的BFS思路很像,但是加了搜索的规则(即入度为零的先被搜索)可以看点这里

AC代码

#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
using namespace std;
const int N = 1e5 + 10;
int e[N],ne[N],h[N],idx,in[N],n,m,top[N],cnt = 1;
// e,ne,h,idx 邻接表模板
// in 代表每个元素的入度
// top是拓扑排序的序列,cnt代表top中有多少个元素
void add(int a,int b){
    e[idx] = b; ne[idx] = h[a];h[a] = idx ++;
}
bool topsort(){
    queue<int> q;
    int t;
    for(int i = 1;i <= n; ++i)// 将所有入度为0的点加入队列
        if(in[i] == 0) q.push(i);
    while(q.size()){
        t = q.front();//每次取出队列的首部
        top[cnt] = t;//加入到 拓扑序列中
        cnt ++; // 序列中的元素 ++
        q.pop();
        for(int i = h[t];i != -1; i = ne[i]){
            // 遍历 t 点的出边
            int j = e[i];
            in[j] --;// j 的入度 --
            if(in[j] == 0) q.push(j); //如果 j 入度为0,加入队列当中
        }
    }
    if(cnt < n) return 0;
    else return 1;
}
int main(){
    int a,b;
    cin >> n >> m;
    memset(h,-1,sizeof h);//给头节点赋值为-1;
    while(m--){
        cin >> a >> b;
        add(a,b);
        in[b] ++;// a -> b , b的入度++
    }
    if(topsort() == 0) cout << "-1";
    else {
        for(int i = 1;i <= n; ++i){
            cout << top[i] <<" ";
        }
    }
    return 0;
}

到此这篇关于C++详细讲解图的拓扑排序的文章就介绍到这了,更多相关C++拓扑排序内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C语言入门之基础知识详解

    C语言入门之基础知识详解

    这篇文章主要介绍了C语言入门之基础知识详解,文中有非常详细的C语言使用教程及相关基础知识,对正在学习c语言的小伙伴们有非常好的帮助,需要的朋友可以参考下
    2021-04-04
  • C++ 类模板、函数模板全特化、偏特化的使用

    C++ 类模板、函数模板全特化、偏特化的使用

    这篇文章主要介绍了C++ 类模板、函数模板全特化、偏特化的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-02-02
  • 详细了解C语言二叉树的建立与遍历

    详细了解C语言二叉树的建立与遍历

    这篇文章主要介绍了C中二叉树的建立和各种遍历实例代码,涉及树节点的定义,后序遍历,层序遍历,深度优先和广度优先等相关内容,具有一定借鉴价值,需要的朋友可以参考下
    2021-07-07
  • 解析结构体的定义及使用详解

    解析结构体的定义及使用详解

    本篇文章是对结构体的定义以及使用进行了详细的分析介绍,需要的朋友参考下
    2013-05-05
  • 基于getline()函数的深入理解

    基于getline()函数的深入理解

    本篇文章是对getline()函数的使用进行了详细的分析介绍,需要的朋友参考下
    2013-05-05
  • C++重载运算符的规则详解

    C++重载运算符的规则详解

    运算符重载函数可以是类的成员函数,也可以是类的友元函数,还可以是既非类的成员函数也不是友元函数的普通函数
    2013-10-10
  • C语言 不使用strcat函数实现连接两个字符串功能代码

    C语言 不使用strcat函数实现连接两个字符串功能代码

    今天小编就为大家分享一篇C语言 不使用strcat函数实现连接两个字符串功能代码,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-12-12
  • 详解C语言中双向循环链表的实现

    详解C语言中双向循环链表的实现

    双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。本文将用C语言实现双向循环链表,需要的可以参考一下
    2022-06-06
  • c语言和c++语言中const修饰的变量区别浅析

    c语言和c++语言中const修饰的变量区别浅析

    这篇文章主要给大家介绍了关于c语言和c++语言中const修饰的变量区别的相关资料,文中通过实例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2022-02-02
  • QT中QByteArray与char、int、float之间的互相转化

    QT中QByteArray与char、int、float之间的互相转化

    本文主要介绍了QT中QByteArray与char、int、float之间的互相转化,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-05-05

最新评论