二叉树的层序遍历
层序遍历一个二叉树。就是从左到右一层一层的去遍历二叉树。
需要借用一个辅助数据结构即队列来实现,队列先进先出,符合一层一层遍历的逻辑,而用栈先进后出适合模拟深度优先遍历也就是递归的逻辑。而这种层序遍历方式就是图论中的广度优先遍历,只不过我们应用在二叉树上。
代码思路
- 定义此函数,返回值类型为二维动态数组,函数的参数为二叉树的根节点
- 定义一个队列,判断根节点是否为空,不为空的话,将根节点加入队列中
- 定义一个二维动态数组result
- while循环,条件为队列非空,定义变量size存储队列的长度,定义一个一维动态数组vec,存储一层的元素值
- 定义一个for循环,以size为循环变量,
- 循环体中定义一个临时节点node,存储队头元素,弹出队头元素,将临时节点node的值存入vec中
- 如果node的左子节点不为空,就将左子节点加入队列中
- 如果node的右子节点不为空,那就将右子节点加入队列中,
- for循环结束,将vec作为一个元素加入到result,
- while循环结束,返回result数组
实现代码
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>>result;
queue<TreeNode*>que;
if(root != nullptr)
{
que.push(root);
}
while(!que.empty())
{
int size = que.size();
vector<int>vec;
for(int i=0;i<size;i++)
{
TreeNode* node = que.front();
que.pop();
vec.push_back(node->val);
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
}
result.push_back(vec);
}
return result;
}
};
关于结构体指针的用法
- TreeNode 是什么?
TreeNode 是一个用户定义的类型,是用 struct 关键字定义的结构体。结构体 TreeNode 定义了一个二叉树节点的数据结构,包括它的值(val)和指向左右子节点的指针(left 和 right)。
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode() : val(0), left(nullptr), right(nullptr) {}
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};
- TreeNode* 是什么?
TreeNode* 表示指向 TreeNode 结构体类型的指针。也就是说,TreeNode* 是一个指针类型,用来存储 TreeNode 结构体对象的内存地址。这个指针用在函数定义的参数中时,意思就是这个函数的接收参数类型就是TreeNode类型的
TreeNode root; // root 是一个 TreeNode 类型的对象
TreeNode* node = &root; // node 是一个指向 root 的指针
226.翻转二叉树
题干
翻转一棵二叉树。
题解思路
两种方法,递归法,迭代法
递归法
递归三部曲:
- 确定递归函数的参数和返回值
返回值其实不需要,但是题目中给出的要返回root节点的指针,可以直接使用题目定义好的函数,所以就函数的返回类型为TreeNode*。 - 确定终止条件
当前节点为空的时候,就返回 - 确定单层递归的逻辑
以前序遍历为例,所以先进行交换左右孩子节点,然后反转左子树,反转右子树。
递归解法可以使用前序和后序遍历,也可以使用中序遍历,只不过中序遍历麻烦一些
题解代码如下
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (root == NULL) return root;
swap(root->left, root->right); // 中
invertTree(root->left); // 左
invertTree(root->right); // 右
return root;
}
};
迭代法
处理节点的操作变为swap其余的操作与遍历二叉树的迭代法一样
class Solution {
public:
TreeNode* invertTree(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->right) st.push(node->right); // 右
if(node->left) st.push(node->left); // 左
}
return root;
}
};
对称二叉树
给定一个二叉树,检查它是否是镜像对称的。
题解思路
其本质还是判断给定二叉树的根节点的两个子树是否可以相互翻转,
就是判断以2为跟节点的两个子树是否可以相互翻转,
递归解法
确定递归的遍历顺序,本题只能用后序遍历,因为后序遍历的顺序是 左右中,收集子节点的信息最后才遍历到根节点
递归法的三个要素:
- 确定递归函数的参数和返回值
因为我们要比较的是根节点的两个子树是否是相互翻转的,进而判断这个树是不是对称树,所以要比较的是两个树,参数自然也是左子树节点和右子树节点。返回值自然是bool类型。 - 确定终止条件
要比较两个节点数值相不相同,首先要把两个节点为空的情况弄清楚!否则后面比较数值的时候就会操作空指针了。
节点为空的情况有:(注意我们比较的其实不是左孩子和右孩子,所以如下我称之为左节点右节点)
左节点为空,右节点不为空,不对称,return false
左不为空,右为空,不对称 return false
左右都为空,对称,返回true
此时已经排除掉了节点为空的情况,那么剩下的就是左右节点不为空:
左右都不为空,比较节点数值,不相同就return false
- 确定单层递归的逻辑
此时才进入单层递归的逻辑,单层递归的逻辑就是处理 左右节点都不为空,且数值相同的情况。
比较二叉树外侧是否对称:传入的是左节点的左孩子,右节点的右孩子。
比较内侧是否对称,传入左节点的右孩子,右节点的左孩子。
如果左右都对称就返回true ,有一侧不对称就返回false 。
题解代码如下
class Solution {
public:
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;
// 此时就是:左右节点都不为空,且数值相同的情况
// 此时才做递归,做下一层的判断
bool outside = compare(left->left, right->right); // 左子树:左、 右子树:右
bool inside = compare(left->right, right->left); // 左子树:右、 右子树:左
bool isSame = outside && inside; // 左子树:中、 右子树:中 (逻辑处理)
return isSame;
}
bool isSymmetric(TreeNode* root) {
if (root == NULL) return true;
return compare(root->left, root->right);
}
};
递归过程图解
104.二叉树的最大深度
题干
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
二叉树的深度和高度
深度:指从根节点到该节点的最长简单路径边的条数或者节点数(大小取决于深度从0开始还是从1开始)
高度: 指从该节点到叶子节点的最长简单路径边的条数或者节点数(大小取决于高度从0开始还是从1开始)
题解思路
本题可以使用前序(中左右),也可以使用后序遍历(左右中),使用前序求的就是深度,使用后序求的是高度。
后序遍历实现的是从底往上的遍历过程,前序遍历是从顶往下的遍历过程
根节点的高度就是二叉树的最大深度
代码如下
class Solution {
public:
int getHight(TreeNode* node)
{
if(node == nullptr) return 0;
int leftHight = getHight(node->left);
int rightHight = getHight(node->right);
int Hight = 1+max(leftHight,rightHight);
return Hight;
}
int maxDepth(TreeNode* root) {
int hight = getHight(root);
return hight;
}
};
111.二叉树的最小深度
题干
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明: 叶子节点是指没有子节点的节点。
题解思路
本题依然是前序遍历和后序遍历都可以,前序求的是深度,后序求的是高度。
二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数或者节点数(取决于深度从0开始还是从1开始)
二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数后者节点数(取决于高度从0开始还是从1开始)
前序就是从根节点依次向下便利的过程,直到遇到叶子节点,这是求深度的过程,
而后序是从下往上遍历的,这是求高度的过程,根据叶子节点的信息处理父节点
那么使用后序遍历,其实求的是根节点到叶子节点的最小距离,就是求高度的过程,不过这个最小距离 也同样是最小深度。
递归法求解
递归三部曲:
- 确定递归函数的参数和返回值
参数为要传入的二叉树根节点,返回的是int类型的深度。 - 确定终止条件
终止条件也是遇到空节点返回0,表示当前节点的高度为0。 - 确定递归逻辑
如果左子树为空,右子树不为空,说明最小深度是 1 + 右子树的深度。
反之,右子树为空,左子树不为空,最小深度是 1 + 左子树的深度。
最后如果左右子树都不为空,返回左右子树深度最小值 + 1
代码实现
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);
}
};
关于递归的理解
引例:以上一个题目求解二叉树的最小深度为例分析
递归函数分为以下四个部分:
- 递归函数的定义
– 明确函数的使命
– 明确原问题和子问题 - 基础情况处理
– 数据规模最小的时候直接返回结果 - 递归调用
– 超级操作(看成一个整体,相信它一定可以完成使命):超级操作的目的是求左右两个子树的最小深度 - 递推到当前层
– 微操作:根据左右两个子树的深度得出当前二叉树的最小深度
设计递归的技巧
第一步设计超级操作,第二部设计微操作,最后设计基础情况处理。这时,我们假设超级操作成立,证明微操作也成立,这时两个定理就形成了互相证明的关系。这跟循环论证没有关系,因为当你证明超级操作成立,就相当于已经找到了一种解答,所以这就是一定是答案。