【数据结构与算法】有向无环图与拓扑排序

有向无环图

若一个有向图中不存在环,则称这个有向图为有向无环图,简称DAG图。

例如:

1
2
3
4
5

这就是一个DAG图

DAG图的应用很广泛,但是最常用的地方是用来表示活动网络,例如,后台常用的任务调度工具dolphinscheduler,核心就是用DAG图来描述工作流。

拓扑排序

在图论中,由一个DAG图的顶点组成的序列,当且仅当满足下列条件时,称该序列为该图的一个拓扑排序:

  1. 每个顶点出现且只出现一次。
  2. 若顶点A在序列中排在顶点B的前面,则图中不存在从B到A的路径。

实现拓扑排序的算法有很多,最常用的思路如下:

  1. 从DAG图中选择一个没有前驱的顶点并输出。
  2. 从图中删除该节点以及从它出发的有向边。
  3. 重复操作1和2,直至当前的DAG图为空或图中不存在没有前驱的结点。

根据这个思路,我们可以知道,若图中不存在没有前驱的顶点,且DAG图不为空,则说明图中一定含有环路。所以拓扑排序也可以用来判断图中是否有环

依据拓扑排序的思路,我们给一个例子:

对该图进行拓扑排序。

DAG图
2
1
3
4
5

第一轮

  • 选择无前驱的结点,有1。
  • 删除它以及从它出发的有向边。

第一轮操作后的图为:

中间图
输出序列
4
2
5
3
1

第二轮

  • 选择无前驱的结点,有2、3,任选一个,假设选2。
  • 删除它以及从它出发的有向边。

第二轮操作后的图为:

中间图
输出序列
5
3
4
1
2

第三轮

  • 选择无前驱的结点,3、4,任选一个,假设选3。
  • 删除它以及从它出发的有向边。

第三轮操作后的图为:

中间图
输出序列
4
5
1
2
3

第四轮

  • 选择无前驱的结点,有4、5,假设选4。
  • 删除它以及从它出发的有向边,没有边可删。

第四轮操作后的图为:

中间图
输出序列
5
1
2
3
4

第五轮

  • 选择无前驱的结点,有5
  • 删除它以及从它出发的有向边,没有边可删。

第五轮操作后的图为:

输出序列
1
2
3
4
5
中间图

此时图为空,拓扑排序结束。

实现

用队列实现(基于BFS)

// C++

#include <queue>
#include <vector>

/**
 * @param E DAG图的边集,用来判断两点之间是否存在边。
 * @param indegree 各顶点的入度
 *
 * @return 拓扑序列
 */
std::vector<int> topoSort(std::vector<std::vector<bool>> E, std::vector<int> indegree)
{
    std::queue<int> q;
    std::vector<int> res;

    // 将入度为0的顶点加入队列
    for (int i = 0; i < indegree.size(); i++)
    {
        if (indegree[i] == 0)
            q.push(i);
    }

    while (!q.empty())
    {
        // 输出
        int tmp = q.front();
        q.pop();
        res.push_back(tmp);

        // 删除从该结点出发的有向边
        for (int i = 0; i < E[tmp].size(); i++)
        {
            if(E[tmp][i]){
                indegree[i]--;
                if(indegree[i] == 0) q.push(i);
            }
        }
    }

    // 需要判断res.size()==indegree.size()才能确定结果是正确的输出序列,否则表示图中存在环路。
    return res; 
}

用栈实现(基于用队列实现演变)

#include <vector>
#include <stack>
/**
 * @param E DAG图的边集,用来判断两点之间是否存在边。
 * @param indegree 各顶点的入度
 *
 * @return 拓扑序列
 */
std::vector<int> topoSort(std::vector<std::vector<bool>> E, std::vector<int> indegree)
{
    std::stack<int> s;
    std::vector<int> res;

    // 将入度为0的顶点加入队列
    for (int i = 0; i < indegree.size(); i++)
    {
        if (indegree[i] == 0)
            s.push(i);
    }

    while (!s.empty())
    {
        // 输出
        int tmp = s.top();
        s.pop();
        res.push_back(tmp);

        // 删除从该结点出发的有向边
        for (int i = 0; i < E[tmp].size(); i++)
        {
            if(E[tmp][i]){
                indegree[i]--;
                if(indegree[i] == 0) s.push(i);
            }
        }
    }

    return res;
}

全篇结束,感谢阅读!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值