概述
递归法是二叉树算法题中一种很常见的解法,由于二叉树的特殊结构,用递归法解题时往往会比迭代法省时省力很多。
先来明确一下递归通用的三步法:
确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
二叉树的前中后序都可以用递归法实现,这里不再赘述,我们来看看递归法在实际解题中的应用
例题
一、 104. 二叉树的最大深度
为什么想到递归
求最大深度,我们首先想到将次节点的左子树深度与右子树深度比较,取其较大值再+1(+1表示该节点本身),就是该节点的最大深度。从根节点向下不断遍历,再从叶子结点将深度不断向上返回,就可以用递归实现。
如何实现
1、确定递归函数的参数和返回值
int maxDepth(struct TreeNode* root)
2、确定终止条件
if (root == NULL) {
return 0;
}
3、确定单层递归的逻辑
int leftDepth = maxDepth(root->left);
int rightDepth = maxDepth(root->right);
return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1;
总体代码
int maxDepth(struct TreeNode* root) {
if (root == NULL) {
return 0;
}
int leftDepth = maxDepth(root->left);//求左子树最大深度
int rightDepth = maxDepth(root->right);//求右子树最大深度
return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1;
}
二、111. 二叉树的最小深度
此题乍一看只需将上一题代码返回值改为返回较小值+1即可,但这却忽略了一种情况
即:二叉树根节点只有左子树或右子树 ,这样在上述代码下leftDepth(或rightDepth)为0,所以最终函数会直接返回1(只算根节点),而不去计算叶子结点。
为了避免这种情况,我们在递归中加入以下逻辑:
如果左子树为空,则去计算右子树最小深度
如果右子树为空,则去计算左子树最小深度
代码如下:
int minDepth(struct TreeNode* root) {
if (root == NULL) {
return 0;
}
//如果左右子树其中一个为空,直接去搜寻另一个不为空的子树
if (root->left == NULL) {
int depth = minDepth(root->right);
return depth + 1;
}
if (root->right == NULL) {
int depth = minDepth(root->left);
return depth + 1;
}
//如果左右子树都不为空
int leftdepth = minDepth(root->left);
int rightdepth = minDepth(root->right);
return leftdepth > rightdepth ? rightdepth + 1 : leftdepth + 1;//返回其高度最小值
}
三、110. 平衡二叉树
此题需在上一题的基础上实现,即通过不断地递归,求每个节点的左右子树之差(先通过上题代码求左右子树高度),判断其符不符合平衡二叉树要求
总结:从上至下遍历,先判断父节点是否平衡,在递归分别判断左子树和右子树是否平衡
int maxDepth(struct TreeNode* root) {//求树的深度
if (root == NULL) {
return 0;
}
int leftDepth = maxDepth(root->left);
int rightDepth = maxDepth(root->right);
return leftDepth > rightDepth ? leftDepth+1 : rightDepth+1;
}
bool isBalanced(struct TreeNode* root) {
if (root == NULL) {
return true;
}
int leftDepth = maxDepth(root->left);//不断递归求左子树与右子树的深度
int rightDepth = maxDepth(root->right);
return abs(leftDepth - rightDepth) < 2 //先判断此节点是否平衡
&& isBalanced(root->left) //不断返回子节点是否平衡
&& isBalanced(root->right);
}
四、101. 对称二叉树
总体思路
要判断一颗树是否总体对称,先判断根节点的左子树a和右子树b是否相等(如果相等,则将其视为对称的左右子树ab)
再判断 1、a的左子树和b的右子树是否相等 2、a的右子树和b的左子树是否相等
如果情况1、2都成立,则说明a、b的子树都对称
随后递归将1中a的左子树和b的右子树视为新的对称的左右子树ab,以相同的方法判断其子树是否对称(同时符合1、2两种情况),2中a的右子树和b的左子树视为新的对称的左右子树ab,以相同方法判断
重点(边界判断)
1、如果左右节点都为空,则对称
2、如果左右节点只有一个为空,则不对称
3、左右节点都不为空的情况下,如果值不相等则不对称,如果相等则继续判断其子树是否对称
代码实现
bool compare(struct TreeNode* left, struct TreeNode* right){//判断left和right是否对称
// 首先排除空节点的情况
if (left == NULL && right != NULL)
return false;
else if (left != NULL && right == NULL)
return false;
else if (left == NULL && right == NULL)
return true;
// 排除了空节点,再排除数值不相同的情况
else if (left->val != right->val)
return false;
// 此时就是:左右节点都不为空,且数值相同的情况
// 此时才做递归,做下一层的判断
bool outside = compare(left->left, right->right); //判断外侧子树是否对称
bool inside = compare(left->right, right->left); //判断内侧子树是否对称
bool isSame = outside && inside;
return isSame;
}
bool isSymmetric(struct TreeNode* root) {
if (root == NULL)
return true;
return compare(root->left, root->right);
}
五、257. 二叉树的所有路径
此题用到了深度优先搜索,不断搜寻当前节点的左子树和右子树的所有路径,直到找到叶子节点,则代表一条路径的结束,将此路径记录
//深度优先搜索
void construct_paths(struct TreeNode* root, char** paths, int* returnSize, int* sta, int top) {
//函数作用:递归地构造从根节点到叶子节点的所有路径
//root: 当前遍历的节点 paths: 存储所有路径的数组 returnSize: 当前已经存储的路径数量 sta: 存储当前路径的栈 top: 栈顶索引
if (root != NULL) { // 如果当前节点不为空
if (root->left == NULL && root->right == NULL) { // 当前节点是叶子节点
char* tmp = (char*)malloc(1001); // 分配一个足够大的空间来存储路径
int len = 0; // 当前路径的长度
// 遍历栈,将栈中的节点值用 "->" 连接起来
for (int i = 0; i < top; i++) {
len += sprintf(tmp + len, "%d->", sta[i]);//sprintf返回值是写入的字符数
}
// 添加叶子节点的值到路径的末尾,并去掉最后一个 "->"
sprintf(tmp + len, "%d", root->val);
// 将路径添加到答案数组中
paths[(*returnSize)++] = tmp;
}
else { // 当前节点不是叶子节点
sta[top++] = root->val; // 将当前节点的值压入栈中
// 递归地构造左子树的所有路径
construct_paths(root->left, paths, returnSize, sta, top);
// 递归地构造右子树的所有路径
construct_paths(root->right, paths, returnSize, sta, top);
}
}
}
// 函数:binaryTreePaths
// 功能:返回二叉树的所有路径
// 参数:
// root: 二叉树的根节点
// returnSize: 存储返回的路径数量
char** binaryTreePaths(struct TreeNode* root, int* returnSize) {
char** paths = (char**)malloc(sizeof(char*) * 1001); // 分配足够大的空间来存储所有路径的指针
*returnSize = 0; // 初始化已存储的路径数量
int sta[1001]; // 栈,用于存储当前路径的节点值
// 调用递归函数构造所有路径
construct_paths(root, paths, returnSize, sta, 0);
return paths; // 返回所有路径的数组
}