C++深度优先搜索DFS

相关知识点

图论知识汇总

常用英文

最近公共祖先(Lowest Common Ancestor,简称LCA)
posterity,英语单词,主要用作名词,作名词时译为“子孙,后裔;后代”。

什么是深度优先搜索

深度优先搜索,Depth First Search, 简称DFS。它从初始节点出发,按预定的顺序扩展到下一个节点,然后从下一节点出发继续扩展新的节点,不断递归执行这个过程,直到某个节点不能再扩展下一个节点为止。此时,则返回上一个节点重新寻找一个新的扩展节点。如此搜索下去,直到找到目标节点,或者搜索完所有节点为止。
通俗的说:
每个节点都有选择类表,如果列表为空,则结束。否则依次DFS(x) , x ∈ \in 选择列表。节点中间的数字就是DFS序(时间戳)
在这里插入图片描述
某个节点为cur,它的任意祖先节点为anc,任意后代为pos。
深度优先有如下性质:
一,当DFS(cur)没有开始执行时,DFS(pos)一定没有开始执行。
二,当DFS(cur)执行结束时,DFS(pos)一定执行结束。
三,如果cur是长子节点,则DFS(cur)结束后。DFS(anc)正在执行,DFS(pos)已经执行结束。其它节点,都没有开始执行。
四,当前节点,可能是后代节点的前置条件,比如: 求层次(leve)。后代节点也可能是当前节点的前置条件,如:求后代数量。
五,DFS(cur)时的DFS序为time1 ,结束时的DFS序为time2。 则DFS序为[time1,time2)的节点    ⟺    \iff cur及cur的后代。

注意

某个用例,匿名DFS函数用时900ms,换成成员函数就变成37ms。

经典应用

无向图的DFS。需要cur节点parent,当next == parent 是忽略,否则无需循环{cur,parent,cur,parent ⋯ \cdots }
有向图,一般不需要,因为大部分情况是无环图。

封装类

枚举

CEnumNeiBo 枚举邻接点,如果有环,以第一次访问为准。CEnumChild 枚举孩子、无环、无向图。

class IDFSEnum
{
public:
	virtual void Enum(int cur, std::function<void(int)> fun) =0;
	virtual int NodeCount() = 0;
	virtual void Clear() = 0;
 };

class CEnumNeiBo : public IDFSEnum
{
public:
	CEnumNeiBo(const vector<vector<int>>& vNeiBo) :m_vNeiBo(vNeiBo) {
		Clear();
	}
	virtual void Clear() override{ m_vVisit.assign(m_vNeiBo.size(), false); };
protected:
	virtual void Enum(int cur, std::function<void(int)> fun) override {
		m_vVisit[cur] = true;
		for (const auto& next : m_vNeiBo[cur]) {
			if (m_vVisit[next]) { continue; }
			fun(next);
		}
	}
	virtual int NodeCount() { return m_vNeiBo.size(); };
	const vector<vector<int>>& m_vNeiBo;
	vector<int> m_vVisit;
};

class CEnumChild : public IDFSEnum
{
public:
	CEnumChild(const vector<vector<int>>& vChild) :m_vChild(vChild) {}
protected:
	virtual void Enum(int cur, std::function<void(int)> fun) override {
		for (const auto& next : m_vChild[cur]) {fun(next);}
	}
	virtual int NodeCount() { return m_vChild.size(); };
	virtual void Clear() override {};
	const vector<vector<int>>& m_vChild;
};

枚举各节点的孩子

class CDFSChild
{
public:
	const vector<vector<int>>& DFSChild(IDFSEnum& dfsEnum, int root) {
		dfsEnum.Clear();
		m_vChild.resize(dfsEnum.NodeCount());
		DFS(dfsEnum, root);
		return m_vChild;
	};
protected:
	void DFS(IDFSEnum& dfsEnum, int cur) {
		dfsEnum.Enum(cur, [&](int next) {m_vChild[cur].emplace_back(next); DFS(dfsEnum, next); });
	}
	vector<vector<int>> m_vChild;
};

计算各节点层次

class CDFSLeve 
{
public:
	vector<int>& DFSLeve(IDFSEnum& dfsEnum,int root) {
		dfsEnum.Clear();
		m_vLeve.assign(dfsEnum.NodeCount(), -1);
		m_vLeve[root] = 0;
		DFS(dfsEnum,root);
		return m_vLeve;
	}
protected:
	void DFS(IDFSEnum& dfsEnum, int cur) {
		dfsEnum.Enum(cur, [&](int next) {m_vLeve[next] = m_vLeve[cur] + 1; DFS(dfsEnum, next); });	}
	vector<int> m_vLeve;
};

暴力计算树直径

class CDFSForDiameter 
{
public:
	
	int DFSDiameter(IDFSEnum& dfsEnum, int root) {
		dfsEnum.Clear();
		m_iRet = 1;
		DFS(dfsEnum, root);
		return m_iRet;
	}
protected:
	int m_iRet = 1;//记录树的直径,0个节点的树会出错。
	int DFS(IDFSEnum& dfsEnum,int cur) {
		int left = 0;
		dfsEnum.Enum(cur, [&](int next) {
			auto right = DFS(dfsEnum,next);
			m_iRet = max(m_iRet, left + right + 1);
			left = max(left, right);
			});
		return left + 1;
	}
};

题解

无向树路径难度分
【深度优先搜索】【图论】【树】2646. 最小化旅行的价格总和2238
【图论】【树形dp】【深度优先搜索】2538. 最大价值和与最小价值和的差值2397
【树】【图论】【树路径】【深度优先搜索】2867. 统计树中的合法路径数目2428
C++深度优先搜索(DFS)算法的应用:2791树中可以形成回文的路径数2677
无向图难度分
【深度优先搜索 图论】:2925在树上执行操作以后得到的最大分数1939
【深度优先搜索】【树】【图论】2973. 树中每个节点放置的金币数目2276
【图论】【状态压缩】【树】【深度优先搜索】1617. 统计子树中城市之间最大距离2308
C++深度优先(DFS)算法的应用:2920收集所有金币可获得的最大积分2350
【树】【异或】【深度优先】【DFS时间戳】2322. 从树中删除边的最小分数2391
【深度优先搜索】【树】【C++算法】2003. 每棵子树内缺失的最小基因值2415
【树】【因子数】【数论】【深度优先搜索】2440. 创建价值相同的连通块2460
【动态规划】【树形dp】【深度优先搜索】LCP 26. 导航装置
有向图难度分
【归并排序】【图论】【动态规划】【 深度优先搜索】1569将子数组重新排序得到同一个二叉搜索树的方案数2288
【深度优先】LeetCode1932:合并多棵二叉搜索树2483
【深度优先搜索】【树】【有向图】【推荐】685. 冗余连接 II
其它难度分
【剪枝】【广度优先】【深度优先】488祖玛游戏
【深度优先搜索】【组合数学】【动态规划】1467.两个盒子中球的颜色数相同的概率2356

扩展阅读

视频课程

有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步优快云学院,听白银讲师(也就是鄙人)的讲解。
https://edu.youkuaiyun.com/course/detail/38771

如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.youkuaiyun.com/lecturer/6176

相关下载

想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版
https://download.youkuaiyun.com/download/he_zhidan/88348653

我想对大家说的话
《喜缺全书算法册》以原理、正确性证明、总结为主。
闻缺陷则喜是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛

测试环境

操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。

### 深度优先搜索算法 (DFS) 的实现及应用 #### 一、DFS 算法思想 深度优先搜索是一种用于遍历或搜索树或图的算法。该方法会尽可能深地探索子节点,当无法继续前进时则回退到上一个节点并尝试其他分支[^1]。 #### 二、DFS 算法实现 ##### (一)递归实现 通过函数调用来模拟栈的行为是最直观的方式之一来执行 DFS 。下面是一个简单的 Python 版本: ```python def dfs_recursive(graph, node, visited=None): if visited is None: visited = set() if node not in visited: print(node) visited.add(node) for neighbour in graph[node]: dfs_recursive(graph, neighbour, visited) ``` 此版本利用了Python内置的数据结构`set()` 来跟踪已访问过的顶点集合;每当遇到新的未被标记的位置就打印出来,并对其相位置重复相同的操作直至整个连通分量都被覆盖完毕。 ##### (二)非递归实现 为了更高效地控制内存分配以及处理大规模数据集的情况,可以采用显式的堆栈机制代替隐含于递归中的系统调用栈来进行迭代式 DFS : ```python from collections import deque def dfs_iterative(graph, start_node): stack, path = [start_node], [] while stack: vertex = stack.pop() if vertex in path: continue path.append(vertex) print(vertex) # Add all unvisited neighbors to the top of the stack. for neighbor in reversed(graph[vertex]): if neighbor not in path and neighbor not in stack: stack.append(neighbor) return path ``` 这里使用列表作为 LIFO 队列(即后进先出),每次取出最后一个加入队列里的元素进行扩展操作,同时记录下已经走过的路径防止循环访问同一节点多次。 #### 三、DFS 应用场景 - **图的遍历** 对给定无向图 G=(V,E),从任意起点出发按照某种顺序依次访问所有可达顶点的过程被称为广义上的 “遍历”。而基于 DFS 可以很容易完成这一任务,在实际编码过程中只需稍作修改即可适应不同类型的输入格式[^2]。 - **拓扑排序** 如果目标是找出有向无环图(DAG) 中各事件发生的先后次序,则可通过逆向构建森林的方法得到满足条件的结果序列——具体做法是在常规流程结束后额外维护一个全局变量 `order[]`, 将每轮结束后的当前结点编号压入其中最后反转数组输出即为所求。 - **连通性分析** 利用 DFS 探索网络内部连接情况有助于判断是否存在孤立部分或是计算强/弱联通区域数量等问题。对于大型社交平台而言尤其重要因为这关系着用户体验的好坏程度。 - **回溯算法** 当面临组合优化类难题比如数独游戏求解器设计时往往借助此类技术框架快速枚举潜在方案空间从而找到最优解或者可行解其中之一。 - **迷宫求解** 给定二维网格地图表示障碍物分布状况之后便能运用上述原理规划机器人行走路线确保能够顺利抵达终点而不碰壁撞墙。
评论 41
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

软件架构师何志丹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值