文章目录
二叉搜索树,看这篇:https://blog.csdn.net/m0_37914588/article/details/103752481
平衡二叉树,看这篇:https://blog.csdn.net/m0_37914588/article/details/103754959
红黑树
参考:https://blog.csdn.net/cy973071263/article/details/122543826
当AVL树中插入或删除节点后,平衡被破坏,就不再是一颗二叉树,为了维持平衡状态就要旋转处理,但成本不小,所以提出了红黑树的理论。
红黑树简介**
红黑树是一种自平衡的二叉查找树,是一种高效的查找树。红黑树具有良好的效率,它可在 O(logN) 时间内完成查找、增加、删除等操作。
为什么需要红黑树?**
1、对于二叉搜索树,如果插入的数据是随机的,那么它就是接近平衡的二叉树,平衡的二叉树,它的操作效率(查询,插入,删除)效率较高,时间复杂度是O(logN)。
2、但是可能会出现一种极端的情况,那就是插入的数据是有序的(递增或者递减),那么所有的节点都会在根节点的右侧或左侧,此时,二叉搜索树就变为了一个链表,它的操作效率就降低了,时间复杂度为O(N),所以可以认为二叉搜索树的时间复杂度介于O(logN)和O(N)之间,视情况而定。
3、它是具备了某些特性的二叉搜索树,能解决非平衡树问题,红黑树是一种接近平衡的二叉树(说它是接近平衡因为它并没有像AVL树的平衡因子的概念,它只是靠着满足红黑节点的5条性质来维持一种接近平衡的结构,进而提升整体的性能,并没有严格的卡定某个平衡因子来维持绝对平衡。
红黑树的特性**
- 在讲解红黑树性质之前,先简单了解一下几个概念:
parent:父节点
sibling:兄弟节点
uncle:叔父节点( parent 的兄弟节点)
grand:祖父节点( parent 的父节点)
首先,红黑树是一个二叉搜索树,它在每个节点增加了一个存储位记录节点的颜色,可以是RED,也可以是BLACK;通过任意一条从根到叶子简单路径上颜色的约束,红黑树保证最长路径不超过最短路径的二倍,因而近似平衡(最短路径就是全黑节点,最长路径就是一个红节点一个黑节点,当从根节点到叶子节点的路径上黑色节点相同时,最长路径刚好是最短路径的两倍)。
节点是红色或黑色
根是黑色
叶子节点(外部节点,空节点)都是黑色,这里的叶子节点指的是最底层的空节点(外部节点),下图中的那些null节点才是叶子节点,null节点的父节点在红黑树里不将其看作叶子节点
红色节点的子节点都是黑色
1、红色节点的父节点都是黑色
2、从根节点到叶子节点的所有路径上不能有 2 个连续的红色节点
从任一节点到叶子节点的所有路径都包含相同数目的黑色节点
红黑树的效率**
红黑树的查找,插入和删除操作,时间复杂度都是O(logN)。
查找操作时,它和普通的相对平衡的二叉搜索树的效率相同,都是通过相同的方式来查找的,没有用到红黑树特有的特性。
但如果插入的时候是有序数据,那么红黑树的查询效率就比二叉搜索树要高了,因为此时二叉搜索树不是平衡树,它的时间复杂度O(N)。
插入和删除操作时,由于红黑树的每次操作平均要旋转一次和变换颜色,所以它比普通的二叉搜索树效率要低一点,不过时间复杂度仍然是O(logN)。总之,红黑树的优点就是对有序数据的查询操作不会慢到O(logN)的时间复杂度。
红黑树和AVL树的比较
- AVL树的时间复杂度虽然优于红黑树,但是对于现在的计算机,cpu太快,可以忽略性能差异
- 红黑树的插入删除比AVL树更便于控制操作
- 红黑树整体性能略优于AVL树(红黑树旋转情况少于AVL树)
红黑树的等价变换
- 红黑树 和 4阶B树(2-3-4树)具有等价性
- 黑色节点与它的红色子节点融合在一起,形成1个B树节点
- 红黑树的黑色节点个数 与 4阶B树的节点总个数相等
- 在所有的B树节点中,永远是黑色节点是父节点,红色节点是子节点。黑色节点在中间,红色节点在两边。
红黑树的操作
前面说到,红黑树是一种自平衡的二叉查找树,既然是二叉查找树的一种,那么查找过程和二叉查找树一样,比较简单。
相对于查找操作,红黑树的插入和删除操作就要复杂的多。尤其是删除操作,要处理的情况比较多
旋转操作
左旋是将某个节点旋转为其右孩子的左孩子,而右旋是节点旋转为其左孩子的右孩子
插入操作
红黑树的插入过程和二叉查找树插入过程基本类似,不同的地方在于,红黑树插入新节点后,需要进行调整,以满足红黑树的性质
删除操作
红黑树的平衡
AVL是靠平衡因子来保持平衡的,比如平衡因子为1,那么左右子树的高度差就不能超过1,是一种强平衡。
对于红黑树而言,为何那5条性质,就能保证红黑树是平衡的?
因为那5条性质,可以保证红黑树等价于4阶B树
B树比较矮,它本身就是平衡的,高度越小越平衡。
红黑树就是能保证这个树高度不会特别高,红黑树的最大高度是 2 ∗ log2(n + 1) ,依然是 O(logn) 级别,因为高度不会很大进而维持一种相对平衡的状态。相比AVL树,红黑树的平衡标准比较宽松:没有一条路径会大于其他路径的2倍。这是是一种弱平衡、黑高度平衡(黑高度只算黑色节点个数,红黑树的任何一条路径的黑色节点数一样,则黑高度都是一样)。
红黑树的平均时间复杂度
搜索:O(logn)
添加:O(logn),O(1) 次的旋转操作
删除:O(logn),O(1) 次的旋转操作
AVL VS 红黑树
AVL树
衡标准比较严格:每个左右子树的高度差不超过1
最大高度是 1.44 ∗ log2 n + 2 − 1.328(100W个节点,AVL树最大树高28)
搜索、添加、删除都是 O(logn) 复杂度,其中添加仅需 O(1) 次旋转调整、删除最多需要 O(logn) 次旋转调整
红黑树
平衡标准比较宽松:没有一条路径会大于其他路径的2倍
最大高度是 2 ∗ log2(n + 1)( 100W个节点,红黑树最大树高40)
搜索、添加、删除都是 O(logn) 复杂度,其中添加、删除都仅需 O(1) 次旋转调整
如何选择
搜索的次数远远大于插入和删除,选择AVL树;搜索、插入、删除次数几乎差不多,选择红黑树
相对于AVL树来说,红黑树牺牲了部分平衡性以换取插入/删除操作时少量的旋转操作,整体来说性能要优于AVL树
红黑树的平均统计性能优于AVL树,实际应用中更多选择使用红黑树
二叉树的种类
在我们解题过程中二叉树有两种主要的形式:满二叉树和完全二叉树。
1.满二叉树
满二叉树:如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。也可以说深度为k,有2^k-1个节点的二叉树。
2.完全二叉树
在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层(h从1开始),则该层包含 1~ 2^(h-1) 个节点。
3.二叉搜索树
前面介绍的树,都没有数值的,而二叉搜索树是有数值的了,二叉搜索树是一个有序树。并不是所有的二叉搜索树都是完全二叉树。
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉排序树
4、平衡二叉搜索树
平衡二叉树一定是二叉搜索树(二叉有序树)
平衡二叉搜索树:又被称为AVL(Adelson-Velsky and Landis)树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。它是在二叉搜索树的基础上增加了高度差这一个定义而已。平衡二叉树概念:树中每个节点的左右子树的高度差不超过1;
C++中map、set、multimap,multiset的底层实现都是平衡二叉搜索树,所以map、set的增删操作时间时间复杂度是logn,注意我这里没有说unordered_map、unordered_set,unordered_map、unordered_set底层实现是哈希表。
二叉树的存储方式
二叉树可以链式存储,也可以顺序存储。
那么链式存储方式就用指针, 顺序存储的方式就是用数组。
顾名思义就是顺序存储的元素在内存是连续分布的,而链式存储则是通过指针把分布在各个地址的节点串联一起。
顺序存储就是用数组来存储树:
如何遍历顺序存储:如果父节点的数组下标是 i,那么它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2。
但是用链式表示的二叉树,更有利于我们理解,所以一般我们都是用链式存储二叉树。
二叉树遍历方式
二叉树主要有两种遍历方式:
深度优先遍历:先往深走,遇到叶子节点再往回走。
广度优先遍历:一层一层的去遍历。
深度优先遍历:
前序遍历(递归法,迭代法)
中序遍历(递归法,迭代法)
后序遍历(递归法,迭代法)
广度优先遍历:
层次遍历(迭代法)
**我们做二叉树相关题目,经常会使用递归的方式来实现深度优先遍历,也就是实现前中后序遍历,使用递归是比较方便的。**之前我们讲栈与队列的时候,就说过栈其实就是递归的一种实现结构,也就说前中后序遍历的逻辑其实都是可以借助栈使用递归的方式来实现的。
而广度优先遍历的实现一般使用队列来实现,这也是队列先进先出的特点所决定的,因为需要先进先出的结构,才能一层一层的来遍历二叉树。
二叉树的定义
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
总结
题目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:
//通过递归实现前序遍历
//1、确定递归函数的参数和返回值:使用vec来存储遍历结果,使用树来进行遍历
//2、确定返回条件,当当前节点是空节点的时候,递归结束;
//3、确定单层逻辑,处理中左右;
void traversal(TreeNode* cur, vector<int>& vec)
{
if(cur == nullptr) return;
vec.push_back(cur->val);
traversal(cur->left, vec);
traversal(cur->right, vec);
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int> result;
traversal(root,result);
return result;
}
};
后序遍历
/**
* 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:
void Traversal(TreeNode* cur, vector<int>& vec)
{
if(cur == nullptr) return;
Traversal(cur->left,vec);
Traversal(cur->right, vec);
vec.push_back(cur->val);
}
vector<int> postorderTraversal(TreeNode* root) {
vector<int> result;
Traversal(root, result);
return result;
}
};
中序遍历
/**
* 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:
void Travesal(TreeNode* cur, vector<int>& vec)
{
if(cur == nullptr) return;
Travesal(cur->left, vec);
vec.push_back(cur->val);
Travesal(cur->right, vec);
}
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;
Travesal(root, result);
return result;
}
};
题目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:
vector<int> preorderTraversal(TreeNode* root) {
//这种迭代也是深度遍历,当遍历完最左侧的深度才往右节点去遍历;
stack<TreeNode*> st;
vector<int> result;
if(root == nullptr) 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;
}
};
中序遍历
因为前序遍历,访问的元素和要处理的元素顺序一致,都是中间节点,所以简单;
但中序遍历,要处理的元素在要访问的元素最后(某一支路而言)
在使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。
/**
* 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> 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
{//在if中查到最后,cur被存入栈中,且cur无左节点;在栈中取出cur节点,处理cur节点;然后查看cur节点有无右节点,
//若无右节点,说明此时cur支树处理完毕,在栈中找到cur节点的父节点;处理父节点,然后查看父节点有无右节点,循环下去
//若有右节点,根据此右节点,有上方的if一直查到最后,然后处理最后的cur节点;然后看cur节点有无右节点;
cur = st.top();//开始处理,如此的话,访问元素的顺序和要处理的顺序就相同了
st.pop();
result.push_back(cur->val);//中
cur = cur->right;//右
}
}
return result;
}
};
后序遍历:
由先序遍历的中左右调整为中右左,在反转一下,就是左右中
/**
* 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> postorderTraversal(TreeNode* root) {
vector<int> result;
TreeNode* node;
stack<TreeNode*> st;
node = root;
st.push(node);
if(root == nullptr) return result;
while(!st.empty())
{ 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;
}
};
题目三:二叉树的层序遍历
层序遍历一个二叉树。就是从左到右一层一层的去遍历二叉树。
**需要借用一个辅助数据结构即队列来实现,队列先进先出,符合一层一层遍历的逻辑,**而用栈先进后出适合模拟深度优先遍历也就是递归的逻辑。
层序遍历
/**
* 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) {
//层序遍历
queue<TreeNode*> que;
if(root != nullptr) que.push(root);
vector<vector<int>> result;
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;
}
};
递归法
class Solution {
public:
void order(TreeNode* cur, vector<vector<int>>& result, int depth)
{
if(cur == nullptr) return ;
//vector<int>() 是一个临时对象(temporary object),表示一个空的 std::vector<int>
if(result.size() == depth) result.push_back(vector<int>());
result[depth].push_back(cur->val);
order(cur->left,result,depth+1);
order(cur->right,result,depth+1);
}
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> result;
int depth = 0;
order(root, result, depth);
return result;
}
};
题目四:二叉树的层序遍历II
使用层序遍历,得到结果后,使用reverse即可;
class Solution {
public:
void order(TreeNode* cur, vector<vector<int>>& result, int depth)
{
if(cur == nullptr) return ;
//vector<int>() 是一个临时对象(temporary object),表示一个空的 std::vector<int>
if(result.size() == depth) result.push_back(vector<int>());
result[depth].push_back(cur->val);
order(cur->left,result,depth+1);
order(cur->right,result,depth+1);
}
vector<vector<int>> levelOrderBottom(TreeNode* root) {
vector<vector<int>> result;
int depth = 0;
order(root, result, depth);
reverse(result.begin(),result.end());
return result;
}
};
题目五:二叉树的右视图
层序遍历的时候,判断是否遍历到单层的最后面的元素,如果是,就放进result数组中,随后返回result就可以了。
class Solution {
public:
vector<int> rightSideView(TreeNode* root) {
queue<TreeNode*> que;
vector<int> result;
if(root != nullptr) que.push(root);
while(!que.empty())
{
int size = que.size();
for(int i = 0; i < size; i++)
{
TreeNode* node = que.front();
que.pop();
if(i == size - 1) result.push_back(node->val);
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
}
}
return result;
}
};
题目六:二叉树的层平均值
将每层的平均值放入结果集即可;
class Solution {
public:
vector<double> averageOfLevels(TreeNode* root) {
queue<TreeNode*> que;
if(root != nullptr) que.push(root);
vector<double> result;
while(!que.empty())
{
double sum = 0;
int size = que.size();
for(int i = 0; i < size; i++)
{
TreeNode* node = que.front();
que.pop();
sum += node->val;
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
}
result.push_back(sum / size);
}
return result;
}
};
题目七:N叉树的层序遍历
数清楚是N就好,依次加入;
/*
// 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) {
queue<Node*> que;
if(root != nullptr) que.push(root);
vector<vector<int>> result;
while(!que.empty())
{
int size = que.size();
vector<int> vec;
for(int i = 0; i < size; i++)
{
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]);//因为给的树有null,所以要判断一下;
}
}
result.push_back(vec);
}
return result;
}
};
题目八:在每个树行中找最大值
在每一行中比较出最大值,然后添加到结果集
/**
* 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;
if(root != nullptr) que.push(root);
vector<int> result;
while(!que.empty())
{
int maxValue = INT_MIN;
int size = que.size();
for(int i = 0; i < size; i++)
{
TreeNode* node = que.front();
que.pop();
maxValue = maxValue > node->val ? maxValue : node->val;
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
}
result.push_back(maxValue);
}
return result;
}
};
题目十:填充每个节点的下一个右侧节点指针
每层的头结点要单独处理,要记录下头结点,然后让头结点指向后面的节点;然后让每层的末尾节点指向nullptr;
/*
// 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 != nullptr) que.push(root);
while(!que.empty())
{
int size = que.size();
Node* prenode;
Node* node;
for(int i = 0; i < size; i++)
{
if(i == 0)
{
prenode = que.front();
que.pop();
node = prenode; //这是为了后面统一处理
}
else
{
node = que.front();
que.pop();
prenode->next = node;
prenode = prenode->next;
}
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
}
prenode->next = NULL; // 本层最后一个节点指向NULL
}
return root;
}
};
题目十一:填充每个节点的下一个右侧节点指针
这个题和题目十是一摸一样的代码
/*
// 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 != nullptr) que.push(root);
while(!que.empty())
{
int size = que.size();
Node* prenode;
Node* node;
for(int i = 0; i < size; i++)
{
if(i == 0)
{
prenode = que.front();
que.pop();
node = prenode; //这是为了后面统一处理
}
else
{
node = que.front();
que.pop();
prenode->next = node;
prenode = prenode->next;
}
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
}
prenode->next = NULL; // 本层最后一个节点指向NULL
}
return 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:
int maxDepth(TreeNode* root) {
queue<TreeNode*> que;
if(root != nullptr) que.push(root);
int depth = 0;
while(!que.empty())
{
int size = que.size();
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);
}
depth++;
}
return depth;
}
};
题目十三:二叉树的最小深度
题还也可以使用层序遍历的方式来解决,思路是一样的。
需要注意的是,只有当左右孩子都为空的时候,才说明遍历的最低点了。如果其中一个孩子为空则不是最低点
/**
* 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;
if(root != nullptr) que.push(root);
int depth = 0;
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;//&&具有短路特性,注意left在前边
}
}
return depth;
}
};
层序遍历的总结:
二叉树的层序遍历,就是图论中的广度优先搜索在二叉树中的应用,需要借助队列来实现
层序遍历遍历相对容易一些,只要掌握基本写法(也就是框架模板),剩下的就是在二叉树每一行遍历的时候做做逻辑修改。
翻转二叉树
想要翻转它,其实就把每一个节点的左右孩子交换一下就可以了
关键在于遍历顺序,前中后序应该选哪一种遍历顺序?
这道题目使用前序遍历和后序遍历都可以,唯独中序遍历不方便,因为中序遍历会把某些节点的左右孩子翻转了两次!
递归法
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if(root == nullptr) return {};
swap(root->left,root->right);
invertTree(root->left);
invertTree(root->right);
return root;
}
};
迭代法
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
stack<TreeNode*> st;
if(root == nullptr) return root;
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);
}
return root;
}
};
层序遍历
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
queue<TreeNode*> que;
if(root == nullptr) return root;
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;
}
};
完全二叉树的节点个数
单层逻辑:确定左子树的节点和右子树的节点数量,然后加上当前节点,就是总共的数量;
class Solution {
public:
int getNodesNum(TreeNode* cur)
{
if(cur == nullptr) return 0;
int leftNum = getNodesNum(cur->left);
int rightNum = getNodesNum(cur->right);
int treeNum = leftNum + rightNum + 1;
return treeNum;
}
int countNodes(TreeNode* root) {
return getNodesNum(root);
}
};
精简版本;
class Solution {
public:
int countNodes(TreeNode* root) {
if(root == nullptr) return 0;
return 1 + countNodes(root->left) + countNodes(root->right);
}
};
层序遍历解法:
class Solution {
public:
int countNodes(TreeNode* root) {
queue<TreeNode*> que;
if(root != nullptr) que.push(root);
int result = 0;
while(!que.empty())
{
int size = que.size();
for(int i = 0; i < size; i++)
{
TreeNode* node = que.front();
que.pop();
result++;
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
}
}
return result;
}
};