目录
一、二叉树理论基础
二叉树的种类
①满二叉树---即每一层节点都是满的,可以说有二叉树深度为K的话,对应的节点就有2^k - 1。
②完全二叉树---常用的堆,就是一颗完全二叉树,同时保证父子节点的顺序关系。优先级队列底层实现就是一个堆。
③二叉搜索树---相较于满二叉树和完全二叉树,这个节点是有数值,二叉搜索树是一个有序树。
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉排序树
下面这两棵树都是搜索树
④平衡二叉搜索树---它是一颗空树或它的左右两个字数的高度差的绝对值不超过1,并且左右两个子树都是一颗平衡二叉树。
C++中map、set、multimap、multiset的底层实现都是平衡二叉搜索树。
二叉树的定义
struct TreeNode{ int val; TreeNode *left; TreeNode *right; TreeNode(int x) : val(x), left(NULL), right(NULL){} };
二叉树的遍历方式
①深度优先遍历:先往深走,遇到叶子节点再往回走(递归法、迭代法)
- 前序遍历:中左右
- 中序遍历:左中右
- 后序遍历:左右中
②广度优先遍历:一层一层的去遍历(迭代法)
- 层次遍历
二、递归遍历
按照三要素来编写递归:
-
确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
-
确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
-
确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
前序遍历:
1.确定递归函数的参数和返回值:需要存放节点的数值---这里使用Vector,之后一个参数就是二叉树指针,这里不需要处理什么数据,就不需要返回值,返回类型为void。
void traversal(TreeNode* cur, vector<int>& vec)
2.确定终止条件:当前遍历的这个节点是空,本层递归结束。
if (cur == NULL) return;
3.确定单层递归的逻辑。前序遍历是中左右。
vec.push_back(cur->val); // 中 traversal(cur->left, vec); // 左 traversal(cur->right, vec); // 右
class Solution {
public:
void traversal(TreeNode *cur, vector<int>& dep){
if(cur == NULL) return;
dep.push_back(cur->val); //中
traversal(cur->left, dep); //左
traversal(cur->right, dep); //右
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int> result;
traversal(root, result);
return result;
}
};
中序遍历和后序遍历与前序遍历的基本相似,不同点再于单层递归的逻辑。
三、迭代遍历
递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。
①前序遍历是中左右,这里利用栈的特性,先将根节点放入栈中,然后将右孩子加入栈中,再加入左孩子。
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> st;
if(root == NULL) return result;
st.push(root);
while(!st.empty()){
TreeNode* node = st.top(); //中
st.pop();
result.push_back(node->val);
if(node->right) st.push(node->right); //右
if(node->left) st.push(node->left); //左
}
return result;
}
};
②后序遍历:可以借鉴前序遍历的思想,现在从中右左转换成中左右,之后再进行一次翻转即可。
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> st;
if(root == nullptr) return result;
st.push(root);
while(!st.empty()){
TreeNode* node = st.top();
st.pop();
result.push_back(node->val);
if(node->left) st.push(node->left);
if(node->right) st.push(node->right);
}
reverse(result.begin(), result.end());
return result;
}
};
③中序遍历(左中右):这里不能借鉴前序和后序的思路,这里的处理顺序和访问顺序是不一致的,这里需要用指针遍历来帮助访问节点,栈则处理节点上的元素。
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> st;
TreeNode* cur = root;
while (cur != nullptr || !st.empty()){
if(cur != nullptr){
st.push(cur);
cur = cur->left;
}else{
cur = st.top();
st.pop();
result.push_back(cur->val);
cur = cur->right;
}
}
return result;
}
};
四、层序遍历
层序遍历,是按照层数从低到高,同一层从左到右遍历。在这里采用递归法是无法做到的,观察遍历的特点,这里需再加一个队列(先进先出特性)来实现。
在将一层一层的二叉树节点放入队列以及弹出队列的操作中,需要记录一层数据的size。
具体思路和步骤如代码:
102.二叉树的层序遍历 #力扣题目链接
题目描述:
给你二叉树的根节点 root
,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
输入输出描述:
示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]
示例 2:
输入:root = [1]
输出:[[1]]
示例 3:
输入:root = []
输出:[]
提示:
- 树中节点数目在范围 [0, 2000] 内
- -1000 <= Node.val <= 1000
思路和想法:
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
queue<TreeNode*> que;
vector<vector<int>> result;
if(root != NULL) que.push(root); //弹入根节点
while(!que.empty()){
int size = que.size(); //记录size个数
vector<int> vec; //记录某一层的结果
while(size--){
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;
}
};
107.二叉树的层序遍历②#力扣题目链接
题目描述:
给你二叉树的根节点 root ,返回其节点值 自底向上的层序遍历 。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
输入输出描述:
示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:[[15,7],[9,20],[3]]
示例 2:
输入:root = [1]
输出:[[1]]
示例 3:
输入:root = []
输出:[]
提示:
- 树中节点数目在范围 [0, 2000] 内
- -1000 <= Node.val <= 1000
思路和想法:
在102的基础上进行结果翻转。
reverse(result.begin(), result.end());
199.二叉树的右视图#力扣题目链接
题目描述:
给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
输入输出描述:
示例 1:
输入: [1,2,3,null,5,null,4]
输出: [1,3,4]
示例 2:
输入: [1,null,3]
输出: [1,3]
示例 3:
输入: []
输出: []
提示:
- 二叉树的节点个数的范围是 [0,100]
- -100 <= Node.val <= 100
思路和想法:
这道题的核心思想:就是将每一层弹出的最后一个放入到结果数组。
class Solution {
public:
vector<int> rightSideView(TreeNode* root) {
queue<TreeNode*> que;
vector<int> result;
if(root != NULL) que.push(root); //弹入根节点
while(!que.empty()){
int size = que.size(); //记录size个数
while(size--){
TreeNode* node = que.front(); //获取要弹出的节点
que.pop(); //队列弹出
if(size == 0){
result.push_back(node->val); //最后一个弹出的放入结果数组即可
}
if(node->left) que.push(node->left); //获取这个节点的左孩子
if(node->right) que.push(node->right); //获取这个节点的右孩子
}
}
return result;
}
};
637.二叉树的层平均值 #力扣题目链接
题目描述:
给定一个非空二叉树的根节点 root , 以数组的形式返回每一层节点的平均值。与实际答案相差 10^-5 以内的答案可以被接受。
输入输出描述:
示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:[3.00000,14.50000,11.00000]
解释:第 0 层的平均值为 3,第 1 层的平均值为 14.5,第 2 层的平均值为 11 。
因此返回 [3, 14.5, 11] 。
示例 2:
输入:root = [3,9,20,15,7]
输出:[3.00000,14.50000,11.00000]
提示:
- 树中节点数量在 [1, 104] 范围内
- -2^31 <= Node.val <= 2^31 - 1
思路和想法:
这一道题的思路:在每一层的元素弹出时,记录下来并进行加和,在每一层弹出结束后再将平均数放到result数组中。
class Solution {
public:
vector<double> averageOfLevels(TreeNode* root) {
double sum = 0; //记录求和
vector<double> result;
queue<TreeNode*> que;
if(root != NULL) que.push(root);
while(!que.empty()){
int size = que.size();
double count = 0; //记录某一层的元素个数
double sum = 0; //记录所有数的加和
while(size--){
TreeNode* node = que.front();
que.pop();
count++;
sum += node->val;
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
}
result.push_back(sum / count); //将每一层的平均数放到数组里
}
return result;
}
};
429.N叉树的层序遍历#力扣题目链接
题目描述:
给定一个 N 叉树,返回其节点值的层序遍历。(即从左到右,逐层遍历)。
树的序列化输入是用层序遍历,每组子节点都由 null 值分隔(参见示例)。
输入输出描述:
示例 1:
输入:root = [1,null,3,2,4,null,5,6]
输出:[[1],[3,2,4],[5,6]]
示例 2:
输入:root = [1,null,2,3,4,5,null,null,6,7,null,8,null,9,10,null,null,11,null,12,null,13,null,null,14]
输出:[[1],[2,3,4,5],[6,7,8,9,10],[11,12,13],[14]]
提示:
- 树的高度不会超过 1000
- 树的节点总数在 [0, 10^4] 之间
思路和想法:
n叉树和二叉树的层序遍历区别不大,这里将left、right变换成children[i]。
class Solution {
public:
vector<vector<int>> levelOrder(Node* root) {
queue<Node*> que;
vector<vector<int>> result;
if(root != NULL) que.push(root); //弹入根节点
while(!que.empty()){
int size = que.size(); //记录size个数
vector<int> vec; //记录某一层的结果
while(size--){
Node* node = que.front(); //获取要弹出的节点
que.pop(); //队列弹出
vec.push_back(node->val); //将弹出的节点数值放入数组中
for(int i = 0; i < node->children.size(); i++){
if(node->children[i]) que.push(node->children[i]);
}
}
result.push_back(vec); //将一层的数组放入到结果中
}
return result;
}
};
515.在每个树行中找最大值 #力扣题目链接
题目描述:
给定一棵二叉树的根节点 root ,请找出该二叉树中每一层的最大值。
输入输出描述:
示例1:
输入: root = [1,3,2,5,3,null,9]
输出: [1,3,9]
示例2:
输入: root = [1,2,3]
输出: [1,3]
提示:
- 二叉树的节点个数的范围是 [0,10^4]
- -2^31 <= Node.val <= 2^31 - 1
思路和想法:
这道题,在每层进行比较获得最大值并记录。
class Solution {
public:
vector<int> largestValues(TreeNode* root) {
queue<TreeNode*> que;
vector<int> result;
if(root != NULL) que.push(root); //弹入根节点
while(!que.empty()){
int size = que.size(); //记录size个数
int max = INT32_MIN; //记录最大值
while(size--){
TreeNode* node = que.front(); //获取要弹出的节点
que.pop(); //队列弹出
if(node->val > max){ //进行数值的比较,获得最大值
max = node->val;
}
if(node->left) que.push(node->left); //获取这个节点的左孩子
if(node->right) que.push(node->right); //获取这个节点的右孩子
}
result.push_back(max);
}
return result;
}
};
116.填充每个节点的下一个右侧节点指针#力扣题目链接
题目描述:
给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:
struct Node { int val; Node *left; Node *right; Node *next; }
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL
。
初始状态下,所有 next 指针都被设置为 NULL
。
输入输出描述:
示例 1:
输入:root = [1,2,3,4,5,6,7]
输出:[1,#,2,3,#,4,5,6,7,#]
解释:给定二叉树如图 A 所示,你的函数应该填充它的每个 next 指针,以指向其下一个右侧节点,如图 B 所示。序列化的输出按层序遍历排列,同一层节点由 next 指针连接,'#' 标志着每一层的结束。
示例 2:
输入:root = []
输出:[]
提示:
- 树中节点的数量在 [0, 2^12 - 1] 范围内
- -1000 <= node.val <= 1000
思路和想法:
这里类似于链表操作,记录好上一个元素nodePre,之后将next指针指向下一个元素。
class Solution {
public:
Node* connect(Node* root) {
queue<Node*> que;
if(root != NULL) que.push(root); //弹入根节点
while(!que.empty()){
int size = que.size(); //记录size个数
Node* nodePre;
Node* node;
for(int i = 0; i <size; i++){
if(i == 0){
nodePre = que.front();
que.pop(); //队列弹出
node = nodePre;
}else{
node = que.front();
que.pop(); //队列弹出
nodePre->next = node;
nodePre = nodePre->next;
}
if(node->left) que.push(node->left); //获取这个节点的左孩子
if(node->right) que.push(node->right); //获取这个节点的右孩子
}
nodePre->next = NULL;
}
return root;
}
};
104.二叉树的最大深度#力扣题目链接
题目描述:
给定一个二叉树 root ,返回其最大深度。
二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。
输入输出描述:
示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:3
示例 2:
输入:root = [1,null,2]
输出:2
提示:
- 树中节点的数量在 [0, 104] 区间内。
- -100 <= Node.val <= 100
思路和想法:
在遍历的同时,记录层数。
class Solution {
public:
int maxDepth(TreeNode* root) {
queue<TreeNode*> que;
int result = 0; //记录层数
if(root != NULL) que.push(root); //弹入根节点
while(!que.empty()){
int size = que.size(); //记录size个数
while(size--){
TreeNode* node = que.front(); //获取要弹出的节点
que.pop(); //队列弹出
if(node->left) que.push(node->left); //获取这个节点的左孩子
if(node->right) que.push(node->right); //获取这个节点的右孩子
}
result++;
}
return result;
}
};
111.二叉树的最小深度#力扣题目链接
题目描述:
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明:叶子节点是指没有子节点的节点。
输入输出描述:
示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:2
示例 2:
输入:root = [2,null,3,null,4,null,5,null,6]
输出:5
提示:
- 树中节点数的范围在 [0, 105] 内
- -1000 <= Node.val <= 1000
思路和想法:
判断条件:当左右孩子为空时,说明到遍历的最低点。
class Solution {
public:
int minDepth(TreeNode* root) {
if (root == NULL) return 0;
int result = 0;
queue<TreeNode*> que;
que.push(root);
while(!que.empty()) {
int size = que.size();
result++; // 记录最小深度
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 result;
}
}
}
return result;
}
};