(二)二叉树的基础修改构造及属性求解1
- 翻转二叉树
- 递归实现
- 迭代实现(深度遍历)
- 层序实现(广度遍历)
- 对称二叉树
- 递归实现
- 迭代实现(非层序遍历)
- 二叉树的最大深度
- 递归法
- 迭代法(层序遍历)
- 二叉树的最小深度
- 解题思路
- 递归法
- 迭代法(层序遍历)
- 完全二叉树的节点个数
- 普通二叉树
- 完全二叉树
- 平衡二叉树
翻转二叉树
力扣原题链接:226. 翻转二叉树
给定一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。
这道题目是非常经典的题目,也是比较简单的题目
思路:
- 注意只要把每一个节点的左右孩子翻转一下,就可以达到整体翻转的效果**
- 这道题目使用前序遍历和后序遍历都可以,唯独中序遍历不方便,因为中序遍历会把某些节点的左右孩子翻转了两次! 拿纸画一画,就理解了
递归实现
1. 确定递归函数的参数和返回值
参数就是要传入节点的指针,不需要其他参数了,通常此时定下来主要参数,如果在写递归的逻辑中发现还需要其他参数的时候,随时补充。
返回值的话其实也不需要,但是题目中给出的要返回root节点的指针,可以直接使用题目定义好的函数,所以就函数的返回类型为TreeNode*
。
TreeNode* invertTree(TreeNode* root)
2. 确定终止条件
当节点为空的时候,就返回
if (root == NULL) return root;
3. 确定单层递归的逻辑
因为是先前序遍历,所以先进行交换左右孩子节点,然后反转左子树,反转右子树。
swap(root->left, root->right); //中
invertTree(root->left); //左
invertTree(root->right); //右
完整代码:
TreeNode* invertTree(TreeNode* root)
{
if (root == NULL)
return root;
swap(root->left, root->right); // 中
invertTree(root->left); // 左
invertTree(root->right); // 右
return root;
}
后续遍历: 后续遍历顺序是左右中,因此只需要将交换放到最后一个处理即可。
TreeNode* invertTree(TreeNode* root)
{
if (root == NULL)
return root;
invertTree(root->left); // 左
invertTree(root->right); // 右
swap(root->left, root->right); // 中
return root;
}
拓展: 中序遍历
对于中序遍历是否简单的交换程序即可呢?答案是否定的。使用递归的中序遍历,某些节点的左右孩子会翻转两次。
如果非要使用递归中序的方式写,也可以,如下代码就可以避免节点左右孩子翻转两次的情况:
TreeNode* invertTree(TreeNode* root)
{
if (root == NULL)
return root;
invertTree(root->left); // 左
swap(root->left, root->right); // 中
//注意 这里依然要遍历左孩子,因为中间节点已经翻转了
invertTree(root->left); //右
return root;
}
迭代实现(深度遍历)
//迭代法翻转二叉树
TreeNode* invert2Tree(TreeNode* root)
{
if(root == NULL)
return root;
stack<TreeNode*> st;
st.push(root);
while(!st.empty())
{
TreeNode* node = st.top();
st.pop();
swap(node->left, node->right); //中
if(node->left)
st.push(node->left); //左
if(node->right)
st.push(node->right); //右
}
}
如果这个代码不太熟悉的话可以再回顾一下二叉树的迭代遍历。
层序实现(广度遍历)
//层序法翻转二叉树
TreeNode* invert3Tree(TreeNode* root)
{
queue<TreeNode*> que;
int size;
if(root != NULL)
que.push(root);
while(!que.empty())
{
size = que.size();
while(size--)
{
TreeNode* node = que.front();
que.pop();
swap(node->left, node->right); //中
if(node->left)
que.push(node->left); //左
if(node->right)
que.push(node->right); //右
}
}
return root;
}
如果对以上代码不理解,或者不清楚二叉树的层序遍历,可以回顾一下二叉树的层序遍历。
对称二叉树
力扣原题链接:101. 对称二叉树
给定一个二叉树,检查它是否是镜像对称的
对于二叉树是否对称,要比较的是根节点的左子树与右子树是不是相互翻转的,理解这一点就知道了其实我们要比较的是两个树(这两个树是根节点的左右子树),所以在递归遍历的过程中,也是要同时遍历两棵树。
确定遍历顺序:
比较的是两个子树的里侧和外侧的元素是否相等。如图所示:
-
本题遍历只能是“后序遍历”,因为我们要通过递归函数的返回值来判断两个子树的内侧节点和外侧节点是否相等。
-
正是因为要遍历两棵树而且要比较内侧和外侧节点,所以准确的来说是一个树的遍历顺序是左右中,一个树的遍历顺序是右左中。
递归实现
递归三部曲
1. 确定递归函数的参数和返回值
因为我们要比较的是根节点的两个子树是否是相互翻转的,进而判断这个树是不是对称树,所以要比较的是两个树,参数自然也是左子树节点和右子树节点。返回值自然是bool类型。
bool compare(TreeNode* left, TreeNode* right)
2. 确定终止条件
要比较两个节点数值相不相同,首先要把两个节点为空的情况弄清楚! 否则后面比较数值的时候就会操作空指针了。
- 左节点为空,右节点不为空,不对称,
return false
- 左不为空,右为空,不对称
return false
- 左右都为空,对称,
return true
- 左右都不为空,且数值不相等,不对称,
return false
代码如下:
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;
//对称 处理递归单层逻辑
else
{
}
}
3. 确定单层递归的逻辑
此时才进入单层递归的逻辑,单层递归的逻辑就是处理 左右节点都不为空,且数值相同的情况。
- 比较二叉树外侧是否对称:传入的是左节点的左子树,右节点的右子树。
- 比较内侧是否对称,传入左节点的右子树,右节点的左子树。
- 如果左右都对称就返回
true
,有一侧不对称就返回false
。
代码如下:
//判断左右节点是否对称
bool outSide = compare(left->left, right->right); //左子树: 左 //左子树: 右
bool inSide = compare(left->right, right->left); //左子树: 右 //左子树: 左
//返回结果到父节点
if(outSide == true && inSide == true) //左子树: 中 //右子树: 中
return true;
else
return false;
//左子树: 左右中 右子树: 右左中 都是后续遍历
如上代码中,我们可以看出使用的遍历方式,左子树左右中,右子树右左中,所以我把这个遍历顺序也称之为“后序遍历”(尽管不是严格的后序遍历)。
最后递归的C++整体代码如下:
bool compare(TreeNode *left, TreeNode *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;
//对称 处理递归单层逻辑
else
{
//判断左右节点是否对称
bool outSide = compare(left->left, right->right); //左子树: 左 //左子树: 右
bool inSide = compare(left->right, right->left); //左子树: 右 //左子树: 左
//返回结果到父节点
if(outSide == true && inSide == true) //左子树: 中 //左子树: 中
return true;
else
return false;
//左子树: 左右中 右子树: 右左中 都是后续遍历
}
}
}
//掉用递归函数
bool isSymmetric(TreeNode* root)
{
if(root == NULL)
return true;
bool ret = compare(root->left, root->right);
return ret;
}
迭代实现(非层序遍历)
这道题目我们也可以使用迭代法,但要注意,这里的迭代法可不是前中后序的迭代写法,因为本题的本质是判断两个树是否是相互翻转的,其实已经不是所谓二叉树遍历的前中后序的关系了。这里我们可以使用队列来比较两个树(根节点的左右子树)是否相互翻转,(注意这不是层序遍历)
使用队列
通过队列来判断根节点的左子树和右子树的内侧和外侧是否相等,如动画所示:
代码如下:
//迭代实现 使用队列
bool isSymmetric(TreeNode* root)
{
if(root == NULL)
return true;
queue<TreeNode*> que;
//左右子树入队列
que.push(root->left);
que.push(root->right);
while(!que.empty())
{
TreeNode* leftNode = que.front(); que.pop(); //左子树根节点
TreeNode* rightNode = que.front(); que.pop(); //右子树根节点
if(leftNode == NULL && rightNode == NULL) //左右子树都为空 对称
continue;
else if(leftNode == NULL && rightNode != NULL)
return false;
else if(leftNode != NULL && rightNode == NULL)
return false;
//保证节点不为空
if(leftNode -> val == rightNode -> val) //数值相等 继续判断子树
{
que.push(leftNode->left);
que.push(rightNode->right);
que.push(leftNode->right);
que.push(rightNode->left);
}
else //都不为空 但数值不等 不对称
return false;
}
return true;
}
二叉树的最大深度
力扣原题链接:104. 二叉树的最大深度
给定一个二叉树 root ,返回其最大深度。
返回它的最大深度 3
- 二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数或者节点数
- 二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数或者节点数
而根节点的高度就是二叉树的最大深度,所以本题中我们通过后序求的根节点高度来求的二叉树最大深度。
递归法
确定递归函数的参数和返回值: 参数就是传入树的根节点,返回就返回这棵树的深度,所以返回值为int类型。
int getHeight(TreeNode* node)
确定终止条件: 如果为空节点的话,就返回0,表示高度为0
if(node == NULL)
return 0;
确定单层递归的逻辑: 先求它的左子树的深度,再求右子树的深度,最后取左右深度最大的数值 再+1 (加1是因为算上当前中间节点)就是目前节点为根节点的树的深度
int leftDepth = getHeight(node->left); //左
int rightDepth = getHeight(node->right); //右
return 1 + max(leftDepth,rightDepth); //中
所以整体c++代码如下:
//递归法 递归函数
int getHeight(TreeNode* node)
{
if(node == NULL)
return 0;
int leftDepth = getHeight(node->left); //左
int rightDepth = getHeight(node->right); //右
return 1 + max(leftDepth,rightDepth); //中
}
int maxDepth(TreeNode* root) {
return getHeight(root);
}
迭代法(层序遍历)
当然这题也可以使用迭代法,使用层序遍历是最为合适的,因为最大的深度就是二叉树的层数,和层序遍历的方式极其吻合。在二叉树中,一层一层的来遍历二叉树,记录一下遍历的层数就是二叉树的深度,如图所示:
所以这道题的迭代法就是一道模板题,可以使用二叉树层序遍历的模板来解决,只需记录二叉树的层数即可,代码如下:
//使用层序遍历
int maxDepth(TreeNode* root)
{
//入口参数检查
if(root == NULL)
return 0;
int depth = 0; //树深度
int size = 0; //没一层节点个数
queue<TreeNode*> que; //存放节点队列
que.push(root);
while(!que.empty())
{
size = que.size(); //记录当前层节点数量
while(size--)
{
TreeNode* node = que.front();
que.pop();
if(node->left)
que.push(node->left);
if(node->right)
que.push(node->right);
}
size = que.size(); //更新队列大小
depth++;
}
return depth;
}
若对该程序不熟悉或对层次遍历不熟悉的话请,回顾复习二叉树的层序遍历方式。
二叉树的最小深度
力扣原题链接:111. 二叉树的最小深度
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明: 叶子节点是指没有左右子节点的节点。
解题思路
直觉上好像和求最大深度差不多,其实还是差不少的。本题依然可以使用上述的后序求二叉树的高度。
但本题还有一个误区,在处理节点的过程中,最大深度很容易理解,最小深度就不那么好理解,如图:
若完整照搬修改求最大深度的程序,则求得上述概述的最小深度是1,因为在求min时,左侧的子树为空,返回0,因此需要考虑左右子树为空的情况。
递归法
确定递归函数的参数和返回值 参数为要传入的二叉树根节点,返回的是int类型的深度,代码如下:
int getMinHeight(TreeNode* node)
确定终止条件 终止条件也是遇到空节点返回0,表示当前节点的高度为0,代码如下:
if(node == NULL)
return 0;
确定单层递归的逻辑
这块和求最大深度可就不一样了,可能会模仿求最大深度写成如下代码:
int leftDepth = getMinHeight(node->left); //左
int rightDepth = getMinHeight(node->right); //右
return 1 + min(leftDepth, rightDepth); //中
这个代码就犯了此图中的误区:
如果这么求的话,没有左孩子的分支会算为最短深度。
所以,如果左子树为空,右子树不为空,说明最小深度是 1 + 右子树的深度。
反之,右子树为空,左子树不为空,最小深度是 1 + 左子树的深度。 最后如果左右子树都不为空,返回左右子树深度最小值 + 1 。
代码如下:
int leftDepth = getMinHeight(node->left); //左
int rightDepth = getMinHeight(node->right); //右
//中
//左子树为空 返回右侧深度
if(node->left == NULL && node->right != NULL)
return rightDepth + 1;
//右子树为空 返回左侧深度
else if(node->left != NULL && node->right == NULL)
return leftDepth + 1;
//返回左右子树最深度
else
return 1 + min(leftDepth, rightDepth);
遍历的顺序为后序(左右中),可以看出:求二叉树的最小深度和求二叉树的最大深度的差别主要在于处理左右孩子不为空的逻辑。
整体递归代码如下:
int getMinHeight(TreeNode* node)
{
if(node == NULL)
return 0;
int leftDepth = getMinHeight(node->left); //左
int rightDepth = getMinHeight(node->right); //右
//中
//左子树为空 返回右侧深度
if(node->left == NULL && node->right != NULL)
return rightDepth + 1;
//右子树为空 返回左侧深度
else if(node->left != NULL && node->right == NULL)
return leftDepth + 1;
//返回左右子树最深度
else
return 1 + min(leftDepth, rightDepth);
}
int minDepth(TreeNode* root) {
return getMinHeight(root);
}
迭代法(层序遍历)
当然本题还可以使用层序遍历的方式来解决,思路是一样的,如果对于层序遍历或者下列程序不清楚的话,可以回顾二叉树的层序遍历思想和程序设计。层序遍历示意图:
需要注意的是,只有当左右孩子都为空的时候,才说明遍历到最低点了。 如果其中一个孩子不为空则不是最低点
代码如下:
//迭代法 层序遍历
int minDepth(TreeNode* root)
{
if(root == NULL)
return 0;
int depth = 0;
int size = 0;
queue<TreeNode*> que;
que.push(root);
while(!que.empty())
{
size = que.size(); //获取队列大小
depth++;
while(size--)
{
TreeNode* node = que.front();
que.pop();
if(node == NULL)
continue;
if(node->left)
que.push(node->left);
if(node->right)
que.push(node->right);
if(node->left == NULL && node->right == NULL)
return depth;
}
}
return depth;
}
完全二叉树的节点个数
力扣原题链接:222. 完全二叉树的节点个数
给出一棵 完全二叉树 的根节点 root ,求出该树的节点个数。
普通二叉树
首先按照普通二叉树的逻辑来求。这道题目的递归法和求二叉树的深度写法类似, 而迭代法,记录遍历的节点数量就可以了,对于递归就是计算节点的左右子树的节点数量。迭代层序遍历的代码不再赘述,相信通过迭代法做完以上题目后已易如反掌的求解二叉树的节点数量。
递归遍历的顺序依然是后序(左右中),其中递归的完整代入如下所示:
//获取普通二叉树节点数量 递归算法
int getNum(TreeNode* node)
{
if(node == NULL)
return 0;
//后续遍历
int leftNum = getNum(node->left); //左
int rightNum = getNum(node->right); //右
return leftNum + rightNum + 1; //中
}
完全二叉树
完全二叉树只有两种情况,情况一:就是满二叉树,情况二:最后一层叶子节点没有满。
对于情况一,可以直接用 2^树深度 - 1 来计算,注意这里根节点深度为1。
对于情况二,分别递归左孩子,和右孩子,递归到某一深度一定会有左孩子或者右孩子为满二叉树,然后依然可以按照情况1来计算。
完全二叉树情况(一)(二)如图:
因此计算完全二叉树的递归算法即判断左右子树是否为满二叉树,并返回当前节点为根节点的节点个数。
可以看出如果整个树不是满二叉树,就递归其左右孩子,直到遇到满二叉树为止,用公式计算这个子树(满二叉树)的节点数量。
如何判断是否为满二叉树: 在完全二叉树中,如果递归向左遍历的深度等于递归向右遍历的深度,那说明就是满二叉树,如果深度不等,则说明不是满二叉树,如图所示:
判断其子树是不是满二叉树,如果是则利用公式计算这个子树(满二叉树)的节点数量,如果不是则继续递归,那么 在递归三部曲中,第二部:终止条件的写法应该是这样的:
//空节点
if(node == NULL)
return 0;
//满二叉树 结束
int leftDepth = 0;
int rightDepth = 0;
TreeNode* leftNode = node->left;
TreeNode* rightNode = node->right;
//向左遍历
while(leftNode)
{
leftNode = leftNode->left;
leftDepth++;
}
//向右遍历
while(rightNode)
{
rightNode = rightNode->right;
rightDepth++;
}
//左右深度相等 满二叉树
if(leftDepth == rightDepth)
return (2<<leftDepth) - 1;
单层递归的逻辑:
//单层递归逻辑
int leftNum = getNum(node->left); //左
int rightNum = getNum(node->right); //右
return leftNum + rightNum + 1; //中
最后完整的递归程序实现代码如下:
//获取完全二叉树节点数量
//递归算法:判断子树是否为满二叉树
//满二叉树的判断:向左遍历和向右遍历的深度是一致的
//(前提:已经是一棵完全二叉树)
int getNum(TreeNode* node)
{
if(node == NULL)
return 0;
//满二叉树 结束
int leftDepth = 0;
int rightDepth = 0;
TreeNode* leftNode = node->left;
TreeNode* rightNode = node->right;
//向左遍历
while(leftNode)
{
leftNode = leftNode->left;
leftDepth++;
}
//向右遍历
while(rightNode)
{
rightNode = rightNode->right;
rightDepth++;
}
//满二叉树
if(leftDepth == rightDepth)
return (2<<leftDepth) - 1;
//单层递归逻辑
int leftNum = getNum(node->left); //左
int rightNum = getNum(node->right); //右
return leftNum + rightNum + 1; //中
}
int countNodes(TreeNode* root)
{
return getNum(root);
}
平衡二叉树
力扣原题链接:110. 平衡二叉树
给定一个二叉树,判断它是否是 平衡二叉树
本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。
示例 1:
- 二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数。
- 二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数,但leetcode中强调的深度和高度很明显是按照节点来计算的,如图:
因为求深度可以从上到下去查 所以需要前序遍历(中左右),而高度只能从下到上去查,所以只能后序遍历(左右中)
1. 明确递归函数的参数和返回值
参数:当前传入节点。 返回值:以当前传入节点为根节点的树的高度。
// -1 表示已经不是平衡二叉树了,否则返回值是以该节点为根节点树的高度
int getHeight(TreeNode* node)
2. 明确终止条件
递归的过程中依然是遇到空节点了为终止,返回0,表示当前节点为根节点的树高度为0,代码如下:
if (node == NULL) {
return 0;
}
3. 明确单层递归的逻辑
通过左子树高度和其右子树高度的差值判断是否为平衡二叉树,如果差值小于等于1,则返回当前二叉树的高度,否则返回-1,表示已经不是二叉平衡树了。
//计算左子树的高度 并判断是否为平衡二叉树
int leftHeight = getHeight(node->left);
if(leftHeight == -1) return -1;
//计算右子树的高度 并判断是否为平衡二叉树
int rightHeight = getHeight(node->right);
if(rightHeight == -1) return -1;
//左右子树是平衡二叉树 但高度差大于1
if(abs(leftHeight - rightHeight) > 1)
return -1;
else
return 1 + max(leftHeight,rightHeight);
最后本题整体递归代码如下:
class Solution {
public:
int getHeight(TreeNode* node)
{
if(node == NULL)
return 0;
//计算左子树的高度 并判断是否为平衡二叉树
int leftHeight = getHeight(node->left);
if(leftHeight == -1) return -1;
//计算右子树的高度 并判断是否为平衡二叉树
int rightHeight = getHeight(node->right);
if(rightHeight == -1) return -1;
//左右子树是平衡二叉树 但高度差大于1
if(abs(leftHeight - rightHeight) > 1)
return -1;
else
return 1 + max(leftHeight,rightHeight);
}
bool isBalanced(TreeNode* root) {
int ret = getHeight(root);
if(ret != -1)
return true;
else
return false;
}
};