C++二叉树深度探索:DFS函数详解与应用实例
C++二叉树深度探索:DFS函数详解与应用实例
本文将详细介绍C++中二叉树的深度优先搜索(DFS)函数,探讨如何自动更新深度、求最大叶子节点深度,并通过实例展示其应用场景。
二叉树与DFS简介
二叉树是一种常见的数据结构,每个节点最多有两个子节点,分别称为左子节点和右子节点。深度优先搜索(DFS)是一种遍历或搜索树或图的算法,它沿着树的深度遍历节点,尽可能深的搜索树的分支。
DFS函数实现
在C++中,我们可以通过递归或栈来实现DFS。以下是一个递归实现的DFS函数示例:
struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
void dfs(TreeNode* node, int depth) {
if (node == NULL) return;
// 处理当前节点
std::cout << "Node: " << node->val << ", Depth: " << depth << std::endl;
// 递归遍历左子树
dfs(node->left, depth + 1);
// 递归遍历右子树
dfs(node->right, depth + 1);
}
在这个函数中,depth
参数表示当前节点的深度。每次递归调用时,深度自动增加1。
自动更新深度
在DFS函数中,深度的更新是通过递归调用时传递depth + 1
来实现的。这种方式确保了每个节点的深度都能正确计算。在 DFS 的递归实现中,depth 的更新规则如下:
- 进入递归时:每次递归调用时,depth 的值会加 1,表示当前节点的深度比父节点深一层。
- 退出递归时:当递归返回到上一层时,depth 的值会自动恢复到调用前的状态,因为递归调用栈会保存每一层的 depth 值。这种机制是由递归的特性决定的:每次递归调用都会创建一个新的栈帧,保存当前的状态(包括 depth 的值)。当递归返回时,栈帧会被弹出,恢复到上一层调用时的状态。
求最大叶子节点深度
为了求二叉树的最大叶子节点深度,我们可以在DFS过程中记录最大深度:
int maxDepth = 0;
void dfs(TreeNode* node, int depth) {
if (node == NULL) return;
if (node->left == NULL && node->right == NULL) {
// 叶子节点
maxDepth = std::max(maxDepth, depth);
}
dfs(node->left, depth + 1);
dfs(node->right, depth + 1);
}
DFS函数的应用
DFS函数在二叉树中有广泛的应用,包括但不限于:
- 路径搜索:查找从根节点到叶子节点的特定路径。
- 子树检查:检查一个树是否是另一个树的子树。
- 序列化与反序列化:将二叉树转换为字符串,或从字符串重建二叉树。
具体应用实例
假设我们有一个二叉树,表示一个家族的家谱。每个节点代表一个人,左子节点和右子节点分别代表其子女。我们可以使用DFS来查找家族中最长的直系血亲链(即最大叶子节点深度)。
#include <iostream>
#include <algorithm>
struct TreeNode {
std::string name;
TreeNode* left;
TreeNode* right;
TreeNode(std::string n) : name(n), left(NULL), right(NULL) {}
};
int maxDepth = 0;
void dfs(TreeNode* node, int depth) {
if (node == NULL) return;
if (node->left == NULL && node->right == NULL) {
maxDepth = std::max(maxDepth, depth);
}
dfs(node->left, depth + 1);
dfs(node->right, depth + 1);
}
int main() {
TreeNode* root = new TreeNode("祖父");
root->left = new TreeNode("父亲");
root->right = new TreeNode("叔叔");
root->left->left = new TreeNode("儿子");
root->left->right = new TreeNode("女儿");
dfs(root, 1);
std::cout << "家族中最长的直系血亲链深度为: " << maxDepth << std::endl;
return 0;
}
在这个例子中,DFS函数帮助我们找到了家族中最长的直系血亲链深度。
DFS经典例题解析
深度优先搜索(DFS)是一种常用的搜索算法,适用于解决排列、组合、棋盘问题等。下面通过两个经典例题来理解DFS的使用方法,并分析其搜索顺序和实现细节。
7.1 AcWing 824 排列数字
题目描述:给定一个整数 ( n ),将数字 ( 1 ~ n ) 排成一排,输出所有可能的排列,按字典序输出。
输入格式:一个整数 ( n )。
输出格式:每行一个排列,数字之间用空格分隔。
数据范围:( 1 ≤ n ≤ 7 )。
解题思路:
- 搜索顺序:从第一个位置开始,依次枚举每个数字,确保每个数字只使用一次。
- 使用一个数组
path
记录当前排列,一个布尔数组str
记录数字是否被使用过。 - 递归终止条件:当排列长度等于 ( n ) 时,输出结果。
- 回溯时恢复现场,确保下一次搜索的正确性。
代码实现:
#include<iostream>
using namespace std;
const int N = 10;
int path[N]; // 存储当前搜索状态
int n;
bool str[N]; // 判断数字是否被使用过
void dfs(int u) {
if (u == n) { // 搜索到最后一个位置
for (int i = 0; i < n; i++) printf("%d ", path[i]); // 输出结果
puts("");
return;
}
for (int i = 1; i <= n; i++) {
if (!str[i]) { // 如果数字未被使用
path[u] = i; // 记录当前数字
str[i] = true; // 标记为已使用
dfs(u + 1); // 递归到下一个位置
str[i] = false; // 回溯恢复
}
}
}
int main() {
cin >> n; // 输入n
dfs(0); // 从第0个位置开始搜索
return 0;
}
7.2 AcWing 843 n-皇后问题
题目描述:在 ( n × n ) 的国际象棋棋盘上放置 ( n ) 个皇后,使得它们互不攻击(即不在同一行、同一列或同一对角线上)。输出所有可能的摆放方案。
输入格式:一个整数 ( n )。
输出格式:每个方案占 ( n ) 行,每行一个长度为 ( n ) 的字符串,.
表示空,Q
表示皇后。每个方案输出完成后输出一个空行。
数据范围:( 1 ≤ n ≤ 9 )。
解题思路:
- 搜索顺序:枚举每一行的皇后放置位置,确保不冲突。
- 使用三个布尔数组
col
、ug
、aug
分别记录列、主对角线、副对角线是否被占用。 - 递归终止条件:当所有行都放置了皇后时,输出结果。
- 回溯时恢复现场,确保下一次搜索的正确性。
代码实现:
#include<iostream>
using namespace std;
const int N = 20; // 对角线数量为 2n-1,开两倍空间
char g[N][N]; // 存储棋盘状态
int n;
bool col[N], ug[N], aug[N]; // 列、主对角线、副对角线是否被占用
void dfs(int u) {
if (u == n) { // 搜索到最后一行
for (int i = 0; i < n; i++) puts(g[i]); // 输出结果
puts("");
return;
}
for (int i = 0; i < n; i++) {
if (!col[i] && !ug[u + i] && !aug[n - u + i]) { // 当前位置合法
g[u][i] = 'Q'; // 放置皇后
col[i] = ug[u + i] = aug[n - u + i] = true; // 标记为已占用
dfs(u + 1); // 递归到下一行
g[u][i] = '.'; // 回溯恢复
col[i] = ug[u + i] = aug[n - u + i] = false; // 回溯恢复
}
}
}
int main() {
cin >> n; // 输入n
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
g[i][j] = '.'; // 初始化棋盘
dfs(0); // 从第0行开始搜索
return 0;
}
- DFS的核心思想:通过递归枚举所有可能的解,并在搜索过程中剪枝,避免无效搜索。
- 搜索顺序:根据问题特点选择合适的搜索顺序,例如全排列问题按位置枚举,n-皇后问题按行枚举。
- 回溯恢复现场:在递归返回时恢复状态,确保下一次搜索的正确性。
- 剪枝优化:通过条件判断提前终止无效搜索,提高算法效率。
通过以上两个例题,我们可以清晰地理解DFS的实现框架和应用场景。掌握DFS的关键在于理解搜索顺序和回溯机制,并通过练习积累经验。
总结
通过本文,我们详细介绍了C++中二叉树的DFS函数,探讨了如何自动更新深度、求最大叶子节点深度,并通过一个具体的应用实例展示了DFS函数的实际用途。希望这些内容能帮助你更好地理解和应用二叉树和DFS算法。
补充知识:除了DFS,广度优先搜索(BFS)也是遍历二叉树的一种常用方法。BFS使用队列来实现,适合用于查找最短路径等问题。在实际应用中,根据具体需求选择合适的遍历方法至关重要。如果你感兴趣的话,别忘了持续关注后续的文章。