有向图的拓扑排序——DFS
在有向图的拓扑排序——BFS这篇文章中,介绍了有向图的拓扑排序的定义以及使用广度优先搜索(BFS)对有向图进行拓扑排序的方法,这里再介绍另一种方法:深度优先搜索(DFS)。
算法
考虑下面这张图:

首先,我们需要维护一个栈,用来存放DFS到的节点。另外规定每个节点有两个状态:未访问(这里用蓝绿色表示)、已访问(这里用黑色表示)。
任选一个节点开始DFS,比如这里就从0开始吧。

首先将节点0的状态设为已访问,然后节点0的邻居(节点0的出边指向的节点)共有1个:节点2,它是未访问状态,于是顺下去访问节点2。

节点2的状态也设为已访问。节点2有3个邻居:3、4、5,都是未访问状态,不妨从3开始。一直这样访问下去,直到访问到没有出边的节点7。

节点7没有出边了,这时候就将节点7入栈。

退回到节点6,虽然6还有邻居,但是唯一的邻居节点7是已访问状态,也入栈。再次退回,节点4的两个邻居也都已访问,依旧入栈并后退。以此类推,退回到节点2。

节点2有3个邻居,其中节点3和4已访问,但是节点5还未访问,访问节点5。

接下来的步骤是一样的,不再赘述了,直接退回到节点0并将0入栈。

现在,从节点0开始的DFS宣告结束,但是图中还有未访问的节点:节点1,从节点1继续开始DFS。

节点1的邻居节点2已经访问过了,直接将节点1入栈。

至此,整个DFS过程宣告结束。从栈顶到栈底的节点序列1 0 2 5 3 4 6 7就是整个图的一个拓扑排序序列。
实现
这里同样使用类型别名node_t代表节点序号unsigned long long:
using node_t = unsigned long long;
同样使用邻接表来存储图结构,整个图的定义如下:
class Graph {
unsigned long long n;
vector<vector<node_t>> adj;
protected:
void dfs(node_t cur, vector<bool> &visited, stack<node_t> &nodeStack);
public:
Graph(initializer_list<initializer_list<node_t>> list) : n(list.size()), adj({}) {
for (auto &l : list) {
adj.emplace_back(l);
}
}
vector<node_t> toposortDfs();
};
DFS
函数dfs的参数及说明如下:
cur:当前访问的节点。visited:存放各个节点状态的数组,false表示未访问,true表示已访问。初始化为全为false。nodeStack:存放节点的栈。
整个过程如下:
- 首先,我们需要将当前节点的状态设为已访问:
visited[cur] = true;
- 依次检查当前节点的所有邻居的状态:
for (node_t neighbor: adj[cur]) {
// ...
}
- 如果某个节点已访问,则跳过。
if (visited[neighbor]) continue;
- 否则,递归的对该节点进行DFS:
dfs(neighbor, visited, nodeStack);
- 所有邻居检查完后,就将该节点入栈:
nodeStack.push(cur);
整个dfs函数的代码如下:
void Graph::dfs(node_t cur, vector<bool> &visited, stack<node_t> &nodeStack) {
visited[cur] = true;
for (node_t neighbor: adj[cur]) {
if (visited[neighbor]) continue;
dfs(neighbor, visited, nodeStack);
}
nodeStack.push(cur);
}
拓扑排序
我们需要初始化3个数据结构:
sort:存放拓扑排序序列的数组。visited:见上文。nodeStack:见上文。
vector<node_t> sort;
vector<bool> visited(n, false);
stack<node_t> nodeStack;
整个过程如下:
- 依次检查每个节点的状态,如果未访问,则从该节点开始进行DFS:
for (node_t node = 0; node < n; ++node) {
if (visited[node]) continue;
dfs(node, visited, nodeStack);
}
- 此时
nodeStack已经存储了整个拓扑排序序列,我们只需要转移到sort数组并返回即可:
while (!nodeStack.empty()) {
sort.push_back(nodeStack.top());
nodeStack.pop();
}
return sort;
整个代码如下:
vector<node_t> Graph::toposortDfs() {
vector<node_t> sort;
vector<bool> visited(n, false);
stack<node_t> nodeStack;
for (node_t node = 0; node < n; ++node) {
if (visited[node]) continue;
dfs(node, visited, nodeStack);
}
while (!nodeStack.empty()) {
sort.push_back(nodeStack.top());
nodeStack.pop();
}
return sort;
}
测试
代码:
int main() {
Graph graph{{2},
{2},
{3, 4, 5},
{4},
{6, 7},
{4},
{7},
{}};
auto sort = graph.toposortDfs();
cout << "The topology sort sequence is:\n";
for (const auto &node: sort) {
cout << node << ' ';
}
return 0;
}
输出:
The topology sort sequence is:
1 0 2 5 3 4 6 7
复杂度分析
- 时间复杂度:\(O(n+e)\),\(n\)为节点总数,\(e\)为边的总数。其中DFS的时间复杂度为\(O(n+e)\)。
- 空间复杂度:\(O(n)\)(邻接表的空间复杂度为\(O(n+e)\),不计入在内),其中维护
visited数组和nodeStack栈分别需要\(O(n)\)的额外空间。
有向图的拓扑排序——DFS的更多相关文章
- ACM/ICPC 之 拓扑排序+DFS(POJ1128(ZOJ1083)-POJ1270)
两道经典的同类型拓扑排序+DFS问题,第二题较第一题简单,其中的难点在于字典序输出+建立单向无环图,另外理解题意是最难的难点,没有之一... POJ1128(ZOJ1083)-Frame Stacki ...
- 拓扑排序+DFS(POJ1270)
[日后练手](非解题) 拓扑排序+DFS(POJ1270) #include<stdio.h> #include<iostream> #include<cstdio> ...
- 拓扑排序-DFS
拓扑排序的DFS算法 输入:一个有向图 输出:顶点的拓扑序列 具体流程: (1) 调用DFS算法计算每一个顶点v的遍历完成时间f[v] (2) 当一个顶点完成遍历时,将该顶点放到一个链表的最前面 (3 ...
- 有向图和拓扑排序Java实现
package practice; import java.util.ArrayDeque; import java.util.Iterator; import java.util.Stack; pu ...
- CodeForces-1217D (拓扑排序/dfs 判环)
题意 https://vjudge.net/problem/CodeForces-1217D 请给一个有向图着色,使得没有一个环只有一个颜色,您需要最小化使用颜色的数量. 思路 因为是有向图,每个环两 ...
- 有向图的拓扑排序算法JAVA实现
一,问题描述 给定一个有向图G=(V,E),将之进行拓扑排序,如果图有环,则提示异常. 要想实现图的算法,如拓扑排序.最短路径……并运行看输出结果,首先就得构造一个图.由于构造图的方式有很多种,这里假 ...
- Ordering Tasks(拓扑排序+dfs)
Ordering Tasks John has n tasks to do. Unfortunately, the tasks are not independent and the executio ...
- HDU 5438 拓扑排序+DFS
Ponds Time Limit: 1500/1000 MS (Java/Others) Memory Limit: 131072/131072 K (Java/Others)Total Sub ...
- C++编程练习(12)----“有向图的拓扑排序“
设G={V,E}是一个具有 n 个顶点的有向图,V中的顶点序列 v1,v2,......,vn,满足若从顶点 vi 到 vj 有一条路径,则在顶点序列中顶点 vi 必在顶点 vj 之前.则称这样的顶点 ...
- POJ1128 Frame Stacking(拓扑排序+dfs)题解
Description Consider the following 5 picture frames placed on an 9 x 8 array. ........ ........ ... ...
随机推荐
- P1706 全排列问题 方法记录
原题链接 全排列问题 题目描述 按照字典序输出自然数 \(1\) 到 \(n\) 所有不重复的排列,即 \(n\) 的全排列,要求所产生的任一数字序列中不允许出现重复的数字. 输入格式 一个整数 \( ...
- 【软件学习】怎么在Word里面设置MathType分隔符,使公式按照章节自动编号
前提 确保已安装好了MathType,且在设置为Word加载项 若没有安装或设置为加载项,请转至博客另一篇文章: [软件学习]如何下载安装Mathtype,并将其加载至Word 第一步 点击MathT ...
- hadoop集群配置全过程
一.nat配置1.虚拟机->编辑->虚拟网络编辑器->更改设置->移除原VMnet8->加新的VMnet8->点击NAT模式 桥接模式->NAT模式,初始化一 ...
- RAID5 IO处理之重构代码详解
1 作用 当阵列降级时,可以添加一块新盘进行重构,以恢复阵列的冗余. 2 发起重构 可以通过以下命令md并发起重构: mdadm -C /dev/md0 --force --run -l 5 -n 3 ...
- IDEA快速生成数据库表的实体类
IDEA连接数据库 IDEA右边侧栏有个DataSource,可以通过这个来连接数据库,我们先成功连接数据库 点击进入后填写数据库进行连接,注意记得一定要去Test Connection 确保正常连接 ...
- NVIDIA Isaac Gym安装与使用
NVIDIA做的Isaac Gym,个人理解就是一个类似于openai的Gym,不过把环境的模拟这个部分扔到了GPU上进行,这样可以提升RL训练的速度. 官网:https://developer.nv ...
- 3.httprunner-extract/.env/Testcase
参数关联-extract 场景:上一个接口的返回值,传给下一个接口当请求参数 extract:提取变量 $变量名 :引用变量 假设我们有如下场景 用户A登录之后,返回值返回用户UID,我们需要查询该用 ...
- 【神经网络】丢弃法(dropout)
丢弃法是一种降低过拟合的方法,具体过程是在神经网络传播的过程中,随机"沉默"一些节点.这个行为让模型过度贴合训练集的难度更高. 添加丢弃层后,训练速度明显上升,在同样的轮数下测试集 ...
- 恭喜磊哥喜提n+1
昨天下午两点多磊哥突然喊我下楼,第一反应是"这孙子,抽烟就直说,还说个事,你以外你是吉祥村大姐啊". 心里骂完以后我慢慢悠悠下楼了,见他在打电话我先默默点上一支,准备待他结束以后对 ...
- Spark基本知识
Spark基本知识 Spark 是一种基于内存的快速.通用.可扩展的大数据分析计算引擎. spark与hadoop的区别 Hadoop Hadoop 是由 java 语言编写的,在分布式服务器集群上存 ...