拓扑排序 【C++实现】

本文详细介绍了偏序、全序概念及其关系,探讨了拓扑排序在有向图中的定义与应用,重点讲解了Kahn算法和DFS方法实现拓扑排序的过程,包括数据结构和算法步骤。
摘要由CSDN通过智能技术生成

目录

1.两种二元关系(仅学习算法思想的读者可以跳过)

(1)偏序

(2)全序

(3)偏序与全序的关系

2.拓扑排序

(1)广义定义

(2)图论中的定义

(3)拓扑排序的应用

3.Kahn算法实现拓扑排序

(1)数据结构

(2)初始化

(3)排序

4.DFS实现拓扑排序

(1)数据结构

(2)初始化

(3)排序


1.两种二元关系(仅学习算法思想的读者可以跳过)
(1)偏序

非严格偏序(自反偏序):

设R是集合X上的二元关系,若R是自反的、反对称的、传递的,则称R是集合X上的非严格偏序

  • 自反性:对∀a∈X,有aRa
  • 反对称性:对∀a,b∈X,若有aRb、bRa,则有a=b
  • 传递性:对∀a,b,c∈X,若有aRb、bRc,则有aRc

严格偏序(反自反偏序):

设R是集合X上的二元关系,若R是反自反的、反对称的、传递的,则称R是集合X上的严格偏序

  • 反自反性:对∀a∈X,均不存在aRa
(2)全序

非严格全序:

设R是集合X上的二元关系,若R是自反的、反对称的、传递的、完全的,则称R是集合X上的全序

  • 完全性:对∀a,b∈X,均有aRb(或bRa)

严格全序:

设R是集合X上的二元关系,若R是反自反的、反对称的、传递的、完全的,则称R是集合X上的全序

(3)偏序与全序的关系

由上述定义可知,非严格全序就是非严格偏序的一个特例,严格全序就是严格偏序的一个特例。通俗地讲,偏序就是指集合中仅有部分元素可以比较,而对应的全序就是指集合中的所有元素均可比较

2.拓扑排序
(1)广义定义

由某个集合上的一个偏序得到该集合上的一个全序,这个过程称为拓扑排序

(2)图论中的定义

根据广义定义,考虑到无向图顶点的前驱(或后继)关系不是偏序(不满足反对称性),故只能对有向进行拓扑排序,且仅对有向无环图DAG进行拓扑排序,才能得到完整的拓扑序列


定义:

对有向图进行拓扑排序,使得对于图中任意一条弧<u,v>,在拓扑序列中都有顶点u是顶点v的前驱(或顶点v是顶点u的后继)


DAG与其拓扑序列的解释:

对于DAG来说,顶点之间的前驱关系(或后继关系)就是一种严格偏序关系,因为:

  • 反自反性:所有顶点不存在自回路,即所有顶点的前驱(或后继)都不是自身
  • 反对称性:无环使得不会同时有,a是b的前驱(或后继)、b是a的前驱(或后继)
  • 传递性:若a是b的前驱(或后继)、b是c的前驱(或后继),则a是c的前驱(或后继)

DAG的拓扑序列就是一种严格全序关系,因为它不仅满足上面三个性质,还满足:

  • 完全性:对序列中任意两个顶点a和b,都有a是b的前驱(或后继)
(3)拓扑排序的应用

对一个有向图进行拓扑排序,根据是否能够得到一个完整的拓扑序列,来判断这个有向图中是否存在环。在日常应用中,通常都是描述一个项目中的所有事件是否能够按顺序完成,而这些事件之间存在一定的先后关系(通常用AOV(Active On Vertex)网描述)

3.Kahn算法实现拓扑排序

算法思想:BFS

Kahn算法按照与拓扑序列相同的顺序依次选择有向图中的顶点,具体如下:

依次找到一个入度为0的顶点,将它输出,并删除所有以它为弧尾的弧。重复该过程直至有向图的边集为空(对应无环),或有向图中所有顶点的入度均不为0(对应有环)


算法实现:

首先需要根据给定的顶点数和边集合,构造邻接表、并求出所有顶点的入度。

  • T(n)=O(|E|)
  • S(n)=O(|V|+|E|)(邻接表)

然后将所有入度为0的顶点入队(或压栈),每次弹出队首(或栈顶),并将所有以它为直接前驱的顶点的入度都-1,若-1后发现这些顶点的入度为0则需要将它们也入队(或压栈)。重复此过程直至输出所有顶点,或输出的顶点数小于|V|。

  • T(n)=O(|V|+|E|)(每个顶点均访问1次,每条弧均访问1次)
  • S(n)=O(1)

总时空开销为:

  • T(n)=O(|V|+|E|)
  • S(n)=O(|V|+|E|)
(1)数据结构
#include<iostream>
#include<vector>
#include<queue>
#include<stack>

class TopologicalSort{
private:
    std::vector<std::vector<int>> adj;    // 邻接表
    std::vector<int> indegree;            // 记录每个顶点的入度
public:
    TopologicalSort(int n,const std::vector<vector<int>> &arc);
    bool sort(std::vector<int> &res);
};
(2)初始化
TopologicalSort::TopologicalSort(int n,const std::vector<vector<int>> &arc){
    adj.resize(n);
    indegree.resize(n);
    for(auto a:arc){
        adj[a[0]].push_back(a[1]);
        indegree[a[1]]++;
    }
}
(3)排序
bool TopologicalSort::sort(std::vector<int> &res){// res用来返回拓扑序列
    res.clear();
    
    // std::stack<int> stk;
    std::queue<int> que;
    for(int i=0;i<indegree.size();i++){
        if(indegree[i]==0){
            // stk.push(i);
            que.push(i);
        }
    }

    // while(!stk.empty()){
    while(!que.empty()){
        // int vertex=stk.top();
        // stk.pop();
        int vertex=que.front();
        que.pop();

        res.push_back(vertex);
        for(auto next:adj[vertex]){
            if(--indegree[next]==0){
                // stk.push(next);
                que.push(next);
            }
        }
    }

    return res.size()==adj.size();
}
4.DFS实现拓扑排序

算法思想:DFS后序+栈

任选一个顶点开始进行DFS后序。

假设某一时刻搜索到顶点u,我们先不访问而是将其入栈。之后在某个时刻回溯到顶点u时,我们再访问它,而此时所有以u为前驱的顶点均已访问完(即均在栈中,且u为栈顶)。此时按照后进先出的规则,可以保证顶点u在其所有后继结点的前面输出,满足拓扑排序的定义


算法实现:

有了算法思想,我们只需要队有向图进行有限次DFS后序就可以得到拓扑序列(或检查到图中存在环),另外借助一个一维数组来记录顶点的状态:未搜索、已搜索但未访问已访问。显然,若在某一时刻搜索到已搜索但未访问的顶点,就说明图中存在环。

T(n)=O(|V|+|E|)(每个顶点访问一次,每条边访问一次)

S(n)=O(|V|+|E|)(邻接表+递归工作栈+栈)

(1)数据结构
#include <iostream>
#include <vector>
#include <stack>

class TopologicalSort{
private:
    std::vector<std::vector<int>> adj;    // 邻接表
    std::vector<int> visit;               // 记录每个顶点的状态(0:未搜索、1:已搜索但未访问、2:已访问
    void dfs_R(int src, bool &hasCircle, std::vector<int> &res);
public:
    TopologicalSort(int n,const std::vector<vector<int>> &arc);
    bool sort(std::vector<int> &res);
}
(2)初始化
TopologicalSort::TopologicalSort(int n,const std::vector<vector<int>> &arc){
    adj.resize(n);
    visit.resize(n);
    for(auto a:arc){
        adj[a[0]].push_back(a[1]);
    }
}
(3)排序
bool TopologicalSort::sort(std::vector<int> &res){// res用来返回拓扑序列
    res.clear();
    bool hasCircle=false;

    for (int i=0; i<visit.size() && !hasCircle; i++) {
        if (!visit[i]) {
            dfs_R(i, hasCircle, res);
        }
    }

    reverse(res.begin(),res.end());
    return !hasCircle;
}

void TopologicalSort::dfs_R(int vertex, bool &hasCircle, std::vector<int> &res){
    visit[vertex]=1;                  // 状态置为已搜索
    for(auto v:adj[src]){
        if(visit[v]==0){
            dfs_R(v,hasCircle,res);
            if(hasCircle){
                return;
            }
        }else if(visit[v]==1){        // 已搜索但未访问
            hasCircle=true;
            return;
        }
    }
    
    visit[vertex]=2;                  // 状态置为已访问,然后压栈
    res.push_back(vertex);
}
  • 38
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值