1.理论基础
(1)二叉树的种类
两种主要形式:满二叉树、完全二叉树。
满二叉树:一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上。
完全二叉树:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。
二叉搜索树:前面介绍的树,都没有数值的,而二叉搜索树是有数值的了,二叉搜索树是一个有序树。
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉排序树
平衡二叉搜索树:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。C++中map、set、multimap,multiset的底层实现都是红黑树(平衡二叉搜索树的一种),所以map、set的增删操作时间时间复杂度是logn。
(2)二叉树的存储方式
可以链式存储(用指针),也可以顺序存储(用数组)。
所以顺序存储的元素在内存是连续分布的,而链式存储是通过指针把分布在各个地址的节点串联在一起。
用数组存储二叉树如何遍历?如果父节点的数组下标是 i,那么它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2。
但我们一般用链式存储。
(3)二叉树的遍历方式
二叉树主要有两种遍历方式:
- 深度优先遍历:先往深走,遇到叶子节点再往回走。可以借助栈来实现。
- 前序遍历(递归法,迭代法)
- 中序遍历(递归法,迭代法)
- 后序遍历(递归法,迭代法)
- 广度优先遍历:一层一层的去遍历。一般用队列来实现。
- 层次遍历(迭代法)
深度优先遍历中的前中后,指的是中间节点的遍历顺序。
- 前序遍历:中左右
- 中序遍历:左中右
- 后序遍历:左右中
(4)二叉树的定义
struct TreeNode
{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x): val(x), left(NULL), right(NULL) {}
//这个是二叉树节点的构造函数
}
如果有构造函数,那么new一个新节点就可以是
TreeNode* a = new TreeNode(9);
没有构造函数的话就是
TreeNode* a = new TreeNode();
a->val=9;
a->left=NULL;
a->right=NULL;
2.二叉树的递归遍历
递归算法三要素:
- 确定递归函数的参数和返回值: 因为要打印出前序遍历节点的数值,所以参数里需要传入vector来放节点的数值,除了这一点就不需要再处理什么数据了也不需要有返回值,所以递归函数返回类型就是void。
- 确定终止条件: 递归的过程中,如何算是递归结束了呢,当然是当前遍历的节点是空了,那么本层递归就要结束了,所以如果当前遍历的这个节点是空,就直接return。
- 确定单层递归的逻辑:前序遍历是中左右的循序,所以在单层递归的逻辑,是要先取中节点的数值,中序和后序同理。
前序:144
class Solution {
public:
//先要自己写一个前序遍历的递归
void traversal(TreeNode* cur, vector<int>& vec)//传入一个节点指针和用来存放遍历结果的数组
{
if(cur==NULL) 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;
}
};
后序:145
class Solution {
public:
//先自己写一个后序的递归
void traversal(TreeNode* cur, vector<int>& vec)
{
if(cur==NULL) 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;
}
};
中序:94
class Solution {
public:
//先自己写一个中序遍历
void traversal(TreeNode* cur, vector<int>& vec)
{
if(cur==NULL) return;
traversal(cur->left,vec);
vec.push_back(cur->val);
traversal(cur->right,vec);
}
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;
traversal(root,result);
return result;
}
};
递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。
589.N叉树的前序遍历
class Solution {
public:
void traversal(Node* cur, vector<int>& vec)
{
if(cur==NULL) return;
vec.push_back(cur->val);
for(int i=0; i<cur->children.size(); i++)
{
traversal(cur->children[i],vec);
}
}
vector<int> preorder(Node* root) {
//定义一个用来存放结果的数组
vector<int> result;
traversal(root,result);
return result;
}
};
590.N叉树的后序遍历
class Solution {
public:
void traversal(Node* cur, vector<int>& vec)
{
if(cur==NULL) return;
for(int i=0; i<cur->children.size(); i++)
{
traversal(cur->children[i],vec);
}
vec.push_back(cur->val);
}
vector<int> postorder(Node* root) {
vector<int> result;
traversal(root,result);
return result;
}
};
3.二叉树的迭代遍历(深度优先,用栈)
前序:144
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
stack<TreeNode*> st; //定义一个栈来存放二叉树的指针
vector<int> result; //定义一个数组来存放遍历结果
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;
}
};
中序:和前后序的逻辑不一样94
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> st;
TreeNode* cur=root;
while(cur!=NULL || !st.empty()) //没有遍历到叶子节点,或者栈里的元素没有全部弹出,都要继续循环
{
if(cur!=NULL) //没有遍历到叶子节点,就继续一路向左
{
st.push(cur); //把遍历过的节点放进栈里
cur=cur->left;
}
else //遍历到了叶子节点,left为空,就开始处理
{
cur=st.top();
st.pop();
result.push_back(cur->val);
//第一次访问到叶子节点(左)的时候,它的right也为空,那么就要再循环一次
//根据访问的顺序,这一次是把中间节点弹出去,这样就从左节点访问到中间节点了
//中间节点的right不为空,那么就又访问到右节点了
cur=cur->right;
}
}
return result;
}
};
后序:145
前序是中左右,后序先把迭代顺序调整为中右左,然后对数组进行翻转,就可以得到左右中。
class Solution {
public:
vector<int> postorderTraversal(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->left) st.push(node->left);
if(node->right) st.push(node->right);
}
reverse(result.begin(),result.end());
return result;
}
};
4.二叉树的统一迭代法(没懂)
5.二叉树层序遍历(队列)
递归和迭代都是深度优先遍历,而层序遍历是广度优先。
栈先进后出适合模拟深度优先遍历,队列先进先出适合一层一层遍历的逻辑。
102 二叉树的层序遍历
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赋值
//size用来表征这一层有多少个元素,就可以用来控制循环,保证在一层的遍历中能够把这一层的元素全部遍历到,不多不少
//后面不能用que.size,因为这个值一直在变
vector<int> vec; //每一层遍历结果存进一个数组
//遍历每一层
for(int i=0; i<size; i++) //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);
//上一层的元素还没有全部弹出的时候,就会有下一层元素加入
//所以用size来表示到底应该有多少元素被弹出
}
//每一层遍历结果放进一个vec里,所以每次循环开始时要重新定义vec
result.push_back(vec);
}
return result;
}
};
107 二叉树的层序遍历II
把102最后得到的数组反转即可
class Solution {
public:
vector<vector<int>> levelOrderBottom(TreeNode* root) {
queue<TreeNode*> que;
vector<vector<int>> result;
if(root!=NULL) 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);
}
reverse(result.begin(),result.end());
return result;
}
};
199.二叉树的右视图
只输出每一层的最右边的元素,加一条判断条件即可,判断到达每一层的最后一个元素再输出。
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();
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;
}
};
637.二叉树的层平均值
思路:遍历每一层并求和,再除以该层对应的size即可
class Solution {
public:
vector<double> averageOfLevels(TreeNode* root) {
queue<TreeNode*> que;
vector<double> result;
if(root!=NULL) que.push(root);
while(!que.empty())
{
int size=que.size();
double sum=0;
//遍历每一层,将这一层的数相加
for(int i=0; i<size; i++)
{
TreeNode* node=que.front();
que.pop();
sum=sum+node->val;
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
}
//每层遍历完得到一个sum,除以本层的size即可得到平均值
result.push_back(sum/size);
}
return result;
}
};
429.N叉树的层序遍历
和二叉树的不同只在孩子的数量上
注意已经不是TreeNode了
class Solution {
public:
vector<vector<int>> levelOrder(Node* root) {
queue<Node*> que; //不是二叉树,就不是treenode了
vector<vector<int>> result;
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 i=0; i<node->children.size(); i++)
{
if(node->children[i]) que.push(node->children[i]);
}
}
result.push_back(vec);
}
return result;
}
};
515.在每个树行中找最大值
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 maxValue=INT_MIN;
for(int i=0; i<size; i++)
{
TreeNode* node=que.front();
que.pop();
maxValue=node->val>maxValue ? node->val : maxValue;
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
}
//每一层得到一个最大值之后,存入数组
result.push_back(maxValue);
}
return result;
}
};
116、117.填充每个节点的下一个右侧节点
116 用的递归
class Solution {
public:
Node* connect(Node* root) {
if(root==NULL) return root;
if(root->left!=NULL)
{
root->left->next=root->right;
if(root->next!=NULL)
{
root->right->next=root->next->left;
}
}
//再把相同的逻辑对左右节点分别走一遍
connect(root->left);
connect(root->right);
return root;
}
};
104.二叉树的最大深度
思路:一层一层遍历二叉树,遍历循环的次数就是二叉树的深度。
class Solution {
public:
int maxDepth(TreeNode* root) {
if(root==NULL) return 0;
int depth=0;
queue<TreeNode*> 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);
}
}
return depth;
}
};
111.二叉树的最小深度
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
只有左右孩子均为空,才到叶子节点。
跟最大深度不同的是,最大深度要把所有层都遍历完才能得出结果,而在最小深度,只要检测到某个节点的左右孩子都为空(即叶子结点),直接返回深度。
class Solution {
public:
int minDepth(TreeNode* root) {
queue<TreeNode*> que;
int depth=0;
if(root==NULL) return 0;
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;
}
};
6.翻转二叉树 226
递归三部曲:函数参数及返回值、递归终止条件、递归逻辑
本题递归解法有三种:前序、后序、中序
前序是先将中间的节点交换,注意交换的时候会带着各自的孩子节点一起交换,然后再以同样的逻辑处理左子树,最后再以同样的逻辑处理右子树。
后序是先处理左子树、再处理右子树,最后写交换中间节点的逻辑。
前序:
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
//递归终止条件
if(root==NULL) return root;
//单层递归逻辑:前序遍历
swap(root->left,root->right); //这里的root不局限于根节点,而是可以指代当前要处理的节点
invertTree(root->left);
invertTree(root->right);
return root;
}
};
后序:
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
//递归终止条件
if(root==NULL) return root;
//单层递归逻辑:后序遍历
invertTree(root->left);
invertTree(root->right);
swap(root->left,root->right); //这里的root不局限于根节点,而是可以指代当前要处理的节点
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;
}
};