层序遍历
给你二叉树的根节点 root
,返回其节点值的 层序遍历
。 (即逐层地,从左到右访问所有节点)。
示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]
示例 2:
输入:root = [1]
输出:[[1]]
示例 3:
输入:root = []
输出:[]
二叉树的深度优先遍历的三种方式:
- 二叉树:前中后序递归法
- 二叉树:前中后序迭代法
- 二叉树:前中后序迭代方式统一写法
二叉树的另一种遍历方式:层序遍历。
层序遍历一个二叉树。就是从左到右一层一层的去遍历二叉树。
此时需要借用一个辅助数据结构即队列来实现, 队列先进先出,符合一层一层遍历的逻辑
,而用栈先进后出适合模拟深度优先遍历也就是递归的逻辑。
而这种层序遍历方式就是图论中的广度优先遍历,只不过我们应用在二叉树上。
/**
* Definition for a binary tree node.
* 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) {}
* };
*/
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> result;
queue<TreeNode*> que;
if(root != NULL) que.push(root);
while(!que.empty()){
vector<int> vec;
int size = que.size();//记录本层的元素个数
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;
}
};
总结
1.用size
记录每层的节点数量,因为queue
中会包含上下层的节点,通过 size
记录的值可以确定队列弹出的元素是属于同一层的。
2.返回的是一个二维数组。每一层的节点都用一个数组来存储,这层遍历完了后再添加进二维数组中。
3.每次访问某一层的节点时,都会将节点的左右孩子添加进队列(若不为空)。
4.二叉树中,广度优先搜索的数据结构用队列;深度优先优先搜索的数据结构用队列。
二叉树的层次遍历 II
给你二叉树的根节点 root ,返回其节点值 自底向上的层序遍历 。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
输入:root = [3,9,20,null,null,15,7]
输出:[[15,7],[9,20],[3]]
示例 2:
输入:root = [1]
输出:[[1]]
示例 3:
输入:root = []
输出:[]
翻转一下返回的数组就是自底向上的效果。
class Solution {
public:
vector<vector<int>> levelOrderBottom(TreeNode* root) {
vector<vector<int>> result;
queue<TreeNode*> que;
if(root != NULL) que.push(root);
while(!que.empty()){
vector<int> vec;
int size = que.size();//记录本层的元素个数
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);//将本层的元素数组添加进结果中
}
reverse(result.begin(),result.end());//翻转result实现自底向上
return result;
}
};
二叉树的右视图
给定一个二叉树的 根节点 root
,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
输入: [1,2,3,null,5,null,4]
输出: [1,3,4]
示例 2:
输入: [1,null,3]
输出: [1,3]
示例 3:
输入: []
输出: []
也就是记录每层的最后一个元素的数值,将其添加进结果中即可。
class Solution {
public:
vector<int> rightSideView(TreeNode* root) {
vector<int> result;
queue<TreeNode*> que;
if(root != NULL) que.push(root);
while(!que.empty()){
int last;
int size = que.size();//记录本层的元素个数
while(size--){//依次弹出
TreeNode* node = que.front();//取队列首元素,从左到右的顺序
que.pop();
last = node->val;//更新本层的节点元素值
if(node->left) que.push(node->left);//添加下一层的节点进队列
if(node->right) que.push(node->right);
}
result.push_back(last);//将本层的最后一个元素添加进结果中
}
return result;
}
};
二叉树的层平均值
给定一个非空二叉树的根节点 root , 以数组的形式返回每一层节点的平均值。与实际答案相差 10^(-5) 以内的答案可以被接受。
输入: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] 。
class Solution {
public:
vector<double> averageOfLevels(TreeNode* root) {
vector<double> result;
queue<TreeNode*> que;
if(root != NULL) que.push(root);
while(!que.empty()){
double num = 0;
int size = que.size();//记录本层的元素个数
for(int i = 0 ; i< size ;i++){//依次弹出
TreeNode* node = que.front();//取队列首元素,从左到右的顺序
que.pop();
num += node->val;//更新本层的节点元素之和
if(node->left) que.push(node->left);//添加下一层的节点进队列
if(node->right) que.push(node->right);
}
result.push_back(num/size);//将本层元素的平均值添加进结果中
}
return result;
}
};
N叉树的层序遍历
给定一个 N 叉树,返回其节点值的层序遍历。(即从左到右,逐层遍历)。
树的序列化输入是用层序遍历,每组子节点都由 null 值分隔(参见示例)。
输入:root = [1,null,3,2,4,null,5,6]
输出:[[1],[3,2,4],[5,6]]
/*
// Definition for a Node.
class Node {
public:
int val;
vector<Node*> children;
Node() {}
Node(int _val) {
val = _val;
}
Node(int _val, vector<Node*> _children) {
val = _val;
children = _children;
}
};
*/
class Solution {
public:
vector<vector<int>> levelOrder(Node* root) {
vector<vector<int>> result;
queue<Node*> que;
if(root != NULL) que.push(root);
while(!que.empty()){
vector<int> vec;
int size = que.size();//记录本层的元素个数
for(int i = 0 ; i< size ;i++){//依次弹出
Node* node = que.front();//取队列首元素,从左到右的顺序
que.pop();
vec.push_back(node->val);//更新本层的节点元素之和
for(int j = 0;j < node->children.size();j++){
que.push(node->children[j]);
}
//添加下一层的节点进队列
}
result.push_back(vec);//将本层元素的平均值添加进结果中
}
return result;
}
};
在每个树行中找最大值
给定一棵二叉树的根节点·root
,请找出该二叉树中每一层的最大值。
输入: root = [1,3,2,5,3,null,9]
输出: [1,3,9]
示例2:
输入: root = [1,2,3]
输出: [1,3]
/**
* Definition for a binary tree node.
* 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) {}
* };
*/
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();
int max = INT_MIN;
while(size--){
TreeNode* node = que.front();
que.pop();
max = max > node->val ? max : node->val;
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
}
result.push_back(max);
}
return result;
}
};
填充每个节点的下一个右侧节点指针
给定一个 完美二叉树·
,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL。
本题依然是层序遍历,只不过在单层遍历的时候记录一下本层的头部节点,然后在遍历的时候让前一个节点指向本节点就可以了
/*
// Definition for a Node.
class Node {
public:
int val;
Node* left;
Node* right;
Node* next;
Node() : val(0), left(NULL), right(NULL), next(NULL) {}
Node(int _val) : val(_val), left(NULL), right(NULL), next(NULL) {}
Node(int _val, Node* _left, Node* _right, Node* _next)
: val(_val), left(_left), right(_right), next(_next) {}
};
*/
class Solution {
public:
Node* connect(Node* root) {
queue<Node*> que;
if(root != NULL) que.push(root);
while(!que.empty()){
int size = que.size();
Node* pre = NULL;//记录前一个节点
for(int i = 0;i < size; i++){
Node* node = que.front();
que.pop();
if(pre != NULL) pre->next = node;//前一个节点不为空时,就指向本次节点
if(i == size-1) node->next = NULL;//对本层的最后一个节点做处理
pre = node;//更新pre节点为本节点
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
}
pre = NULL;
}
return root;
}
};
填充每个节点的下一个右侧节点指针II
给定一个二叉树:
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL 。
初始状态下,所有 next 指针都被设置为 NULL 。
这道题目说是二叉树,但116题目说是完整二叉树,其实没有任何差别,一样的代码一样的逻辑一样的味道。
queue<Node*> que;
if(root != NULL) que.push(root);
while(!que.empty()){
int size = que.size();
Node* pre = NULL;
for(int i = 0;i < size; i++){
Node* node = que.front();
que.pop();
if(pre != NULL) pre->next = node;
if(i == size-1) node->next = NULL;
pre = node;
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
}
pre = NULL;
}
return root;
二叉树的最大深度
给定一个二叉树 root ,返回其最大深度。
二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。
输入:root = [3,9,20,null,null,15,7]
输出:3
输入:root = [1,null,2]
输出:2
思路
使用迭代法的话,使用层序遍历是最为合适的,因为最大的深度就是二叉树的层数,和层序遍历的方式极其吻合。
在二叉树中,一层一层的来遍历二叉树,记录一下遍历的层数就是二叉树的深度
/**
* Definition for a binary tree node.
* 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) {}
* };
*/
class Solution {
public:
int maxDepth(TreeNode* root) {
queue<TreeNode*> que;
int num = 0;
if(root != NULL) que.push(root);
while(!que.empty()){
int 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);
}
num++;
}
return num;
}
};
二叉树的最小深度
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明:叶子节点是指没有子节点的节点。
输入:root = [3,9,20,null,null,15,7]
输出:2
输入:root = [2,null,3,null,4,null,5,null,6]
输出:5
相对于 104.二叉树的最大深度 ,本题还也可以使用层序遍历的方式来解决,思路是一样的。
需要注意的是,只有当左右孩子都为空的时候,才说明遍历的最低点了。如果其中一个孩子为空则不是最低点
/**
* Definition for a binary tree node.
* 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) {}
* };
*/
class Solution {
public:
int minDepth(TreeNode* root) {
queue<TreeNode*> que;
int num = 0;
if(root != NULL) que.push(root);
while(!que.empty()){
int 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);
if(node->left == NULL && node->right == NULL) return ++num;
}
num++;
}
return num;
}
};
总结
二叉树的层序遍历,就是图论中的广度优先搜索在二叉树中的应用,需要借助队列来实现(此时又发现队列的一个应用了)。
翻转二叉树
给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。
输入:root = [4,2,7,1,3,6,9]
输出:[4,7,2,9,6,3,1]
输入:root = [2,1,3]
输出:[2,3,1]
可以发现想要翻转它,其实就把每一个节点的左右孩子交换一下就可以了。
关键在于遍历顺序,前中后序应该选哪一种遍历顺序? (一些同学这道题都过了,但是不知道自己用的是什么顺序)
遍历的过程中去翻转每一个节点的左右孩子就可以达到整体翻转的效果。
注意只要把每一个节点的左右孩子翻转一下,就可以达到整体翻转的效果
这道题目使用前序遍历和后序遍历都可以,唯独中序遍历不方便,因为中序遍历会把某些节点的左右孩子翻转了两次!建议拿纸画一画,就理解了
那么层序遍历可以不可以呢?依然可以的!只要把每一个节点的左右孩子翻转一下的遍历方式都是可以的!
前序遍历
/**
* Definition for a binary tree node.
* 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) {}
* };
*/
class Solution {//前序遍历
public:
TreeNode* invertTree(TreeNode* root) {
stack<TreeNode*> st;
if(root != NULL) st.push(root);
while(!st.empty()){
TreeNode* node = st.top();
st.pop();
if(node->right) st.push(node->right);//先右后左。栈改变出栈顺序。
if(node->left) st.push(node->left);
swap(node->left,node->right);//交换节点
}
return root;
}
};
广度优先遍历
层数遍历也是可以翻转这棵树的,因为层序遍历也可以把每个节点的左右孩子都翻转一遍
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
queue<TreeNode*> que;
if (root != NULL) que.push(root);
while (!que.empty()) {
int size = que.size();
for (int i = 0; i < size; i++) {
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;
}
};
递归法
三部曲:
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);//反转右子树
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;
}
};
递归的中序遍历是不行的,因为使用递归的中序遍历,某些节点的左右孩子会翻转两次。如果非要使用递归中序的方式写,也可以,如下代码就可以避免节点左右孩子翻转两次的情况:
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (root == NULL) return root;
invertTree(root->left); // 左
swap(root->left, root->right); // 中
invertTree(root->left); // 注意 这里依然要遍历左孩子,因为中间节点已经翻转了.实际上是原来没有翻转前的右边部分节点
return root;
}
};
总结
1.确定遍历的顺序,前序、中序、后序
2.确定遍历的方式:迭代法(前序、中序、后序、广度优先搜索)、递归法、
3.交换每个节点的子节点。使用递归的中序遍历,某些节点的左右孩子会翻转两次。
红黑树就是一种二叉平衡搜索树,这两个树不是独立的,所以C++中map、multimap、set、multiset的底层实现机制是二叉平衡搜索树,再具体一点是红黑树。