二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数或者节点数(取决于深度从0开始还是从1开始)
二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数或者节点数(取决于高度从0开始还是从1开始)
由此我们可以得出一个结论:根节点高度 等于 二叉树的最大深度 ,如果我们想求二叉树的某个节点的高度,我们求那个节点所在子树的最大深度即可,这个转化的思想在我们解决 判断一颗二叉树是否是平衡二叉树的问题中会起到关键性的作用。
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
示例: 给定二叉树 [3,9,20,null,null,15,7],
返回它的最大深度 3 。
递归法是解决二叉树问题当中最经典最常见的方法,它拥有代码行数少,精简高效的特点。
class solution {
public:
int getdepth(TreeNode* node) {
if (node == NULL) return 0;
int leftdepth = getdepth(node->left); // 左
int rightdepth = getdepth(node->right); // 右
int depth = 1 + max(leftdepth, rightdepth); // 中
return depth;
}
int maxDepth(TreeNode* root) {
return getdepth(root);
}
};
递归三部曲:确定递归函数的参数和返回值及返回值类型-->确定递归函数的终止条件-->确定递归的单层逻辑。
这个递归实现的遍历方式采取的是后续遍历的方式,由树的底层至树的首节点,每个左右孩子将属于它这颗子树的最大深度通过递归传递回他们的父节点,他们的父节点再通过这样相同的方式传递给他们父节点的父节点,这就是递归的底层逻辑。
在这个递归形式的代码中,我们的树的高度是从1开始计算的,(当然也是可以从0开始计算的),我们的首节点的高度为1。max(leftdepth, rightdepth)的意思是返回最大深度,因为我们二叉树的左右子树不可能完全对称,也就意味着他们的深度并不一定相等,我们要求最大深度,所以就用一个max维护最大深度。
class solution {
public:
int result;
void getdepth(TreeNode* node, int depth) {
result = depth > result ? depth : result; // 中
if (node->left == NULL && node->right == NULL) return ;
if (node->left) { // 左
depth++; // 深度+1
getdepth(node->left, depth);
depth--; // 回溯,深度-1
}
if (node->right) { // 右
depth++; // 深度+1
getdepth(node->right, depth);
depth--; // 回溯,深度-1
}
return ;
}
int maxDepth(TreeNode* root) {
result = 0;
if (root == NULL) return result;
getdepth(root, 1);
return result;
}
};
这个使用前序遍历--中左右--的方法很鲜明的指出了回溯法,这里的回溯法其实就是深度优先搜索,搜索二叉树的每一条,到底后通过回溯返回继续搜索下一条,每搜索一条就更新一次树的深度的大小,最大值就是这颗二叉树的最大深度
首先要明确最小深度的定义,最小深度是 从根节点到最近叶子节点的最短路径上的节点的数量。请注意是到最近叶子节点,不是空节点。
class Solution {
public:
int getDepth(TreeNode* node) {
if (node == NULL) return 0;
int leftDepth = getDepth(node->left); // 左
int rightDepth = getDepth(node->right); // 右
// 中
// 当一个左子树为空,右不为空,这时并不是最低点
if (node->left == NULL && node->right != NULL) {
return 1 + rightDepth;
}
// 当一个右子树为空,左不为空,这时并不是最低点
if (node->left != NULL && node->right == NULL) {
return 1 + leftDepth;
}
int result = 1 + min(leftDepth, rightDepth);
return result;
}
int minDepth(TreeNode* root) {
return getDepth(root);
}
};
这样就可以有效避免将空节点也算作二叉树最小深度的错误。
能用递归实现的程序,基本都可以用栈和队列来实现,而且栈和队列来实现的思路相比递归会更加清晰,易懂。
class Solution {
public:
int minDepth(TreeNode* root) {
if (root == NULL) return 0;
int depth = 0;
queue que;
que.push(root);
while(!que.empty()) {
int size = que.size();
depth++; // 记录最小深度
for (int i = 0; i < size; i++) {
TreeNode* node = que.front();
que.pop();
if (node->left) que.push(node->left);
if (node->right) que.push(node->right);
if (!node->left && !node->right) { // 当左右孩子都为空的时候,说明是最低点的一层了,退出
return depth;
}
}
}
return depth;
}
这个迭代法的本质其实是BFS广度优先搜索,一层一层的遍历搜索树的最小深度,只要找到就立即返回,可以省去很多不必要的操作。
class Solution {
public:
int countNodes(TreeNode root) {
if(root == nullptr) return 0;
//我们可以利用满二叉树求节点数量的公式结合递归来求解完全二叉树的节点个数
//首先设置两个指针--左指针和右指针来检查当前树的最左边和最右边的高度是否相等,若相等则是满二叉树,我们不需要担心树的边的中间有空节点,因为题目中已经规定这棵树是完全二叉树,这就表明树两条边界的中间一定没有空节点
TreeNode *left = root->left;
TreeNode *right = root->right;
int leftDepth = 1;
int rightDepth = 1;
while(left != nullptr) {
left = left->left;
leftDepth++;
}
while(right != nullptr) {
right = right->right;
rightDepth++;
}
if(leftDepth == rightDepth) return (1<left);
int rightTreeNum = countNodes(root->right);
return leftTreeNum + rightTreeNum + 1;
}
};
在递归法中,我们用到了一个已知的常识:满二叉树的节点数量 = 2^满二叉树的深度次方 - 1。
什么是完全二叉树
什么是满二叉树
我们不难发现完全二叉树其实就是由一个个的满二叉树构成的。
这里的递归其实就是树的上层开始搜索是否有满二叉树,有则直接通过求满二叉树的节点公式就可以直接求出一大片的节点数量,省去了一大部分的遍历,这就是这个递归的方法要优于迭代法的原因所在。我们不需要担心判断这棵树是否是满二叉树时,数的最左右两侧的树枝长度相等但是中间可能不相等,其实我们完全没有必要来担心这个问题,因为题中交代给出的是一颗完全二叉树,是不会出现两边等中间不等的情况的。
class Solution {
public:
//题目中已经给出了一颗完全二叉树,我们就不需要自己去判断这棵树是否是完全二叉树了
int countNodes(TreeNode root) {
if(root == nullptr) return 0;
queueque;
if(root != nullptr) que.push(root);
int count = 0; //用来统计节点的数量
while(!que.empty()) {
int size = que.size();
count += size;
while(size--) {
TreeNode *cur = que.front();
que.pop();
if(cur->left != nullptr) que.push(cur->left);
if(cur->right != nullptr) que.push(cur->right);
}
}
return count;
}
一颗平衡二叉树的定义是:一个节点的左右子树的高度差的绝对值不超过1。
class Solution {
public:
int judgeTreeBalance(TreeNode* root) {
if(root == nullptr) return 0;
int leftHeight = judgeTreeBalance(root->left);
if(leftHeight == -1) return -1;
int rightHeight = judgeTreeBalance(root->right);
if(rightHeight == -1) return -1;
if(abs(leftHeight - rightHeight) > 1) return -1;
else return max(leftHeight, rightHeight) + 1;
}
bool isBalanced(TreeNode* root) {
if(root == nullptr) return true;
return judgeTreeBalance(root) == -1 ? false : true;
}
};
我们用-1作为这棵树不是平衡二叉树的信标,若节点收到子节点传回的-1,则继续将-1这个信号继续传给它的父节点,直至传到首节点。若函数返回-1,则我们可以断定,这棵树不是一颗平衡二叉树。
暴力判断:
从上至下遍历每个节点,只要发现不匹配的节点立即返回false报错。
class Solution {
public:
int nodeHeight(TreeNode* cur) {
if(cur == nullptr) return 0;
int leftHeight = nodeHeight(cur->left);
int rightHeight = nodeHeight(cur->right);
return 1 + max(leftHeight, rightHeight);
}
bool isBalanced(TreeNode* root) {
if(root == nullptr) return true;
queueque;
que.push(root);
while(!que.empty()) {
int size = que.size();
while(size--) {
TreeNode *node = que.front(); que.pop();
if(abs(nodeHeight(node->left) - nodeHeight(node->right)) > 1) return false;
if(node->left != nullptr) que.push(node->left);
if(node->right != nullptr) que.push(node->right);
}
}
return true;
}
};