二叉树需要掌握的内容图
递归遍历
这里帮助大家确定下来递归算法的三个要素。每次写递归,都按照这三要素来写,可以保证大家写出正确的递归算法!
确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
题目: 144. 二叉树的前序遍历 - 力扣(LeetCode)
前序遍历
class Solution {
public:
void traversal(TreeNode*cur,vector<int>&res)//地址传入
{
if(cur==0)return;
res.push_back(cur->val);
traversal(cur->left,res);
traversal(cur->right,res);
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int>result;
traversal(root,result);
return result;
}
};
中序遍历
class Solution {
public:
void traversal(TreeNode*cur,vector<int>&res)//地址传入
{
if(cur==0)return;
traversal(cur->left,res);
res.push_back(cur->val);
traversal(cur->right,res);
}
vector<int> inorderTraversal(TreeNode* root) {
vector<int>result;
traversal(root,result);
return result;
}
};
后序遍历
class Solution {
public:
void traversal(TreeNode*cur,vector<int>&res)//地址传入
{
if(cur==0)return;
traversal(cur->left,res);
traversal(cur->right,res);
res.push_back(cur->val);
}
vector<int> postorderTraversal(TreeNode* root) {
vector<int>result;
traversal(root,result);
return result;
}
};
迭代遍历
前序遍历
前序遍历是中左右,每次先处理的是中间节点,那么先将根节点放入栈中,然后将右孩子加入栈,再加入左孩子。
为什么要先加入 右孩子,再加入左孩子呢? 因为这样出栈的时候才是中左右的顺序。
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;
}
};
后序遍历
在前序遍历基础上改动,压入栈的顺序是中左右,然后取出是中右左,最后反转结果数组得到左右中即可。
class Solution {
public:
vector<int> inorderTraversal(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->left)st.push(node->left);
if(node->right)st.push(node->right);
}
reverse(result.begin(),result.end());
return result;
}
};
中序遍历
动图
中序遍历是左中右,先访问的是二叉树顶部的节点,然后一层一层向下访问,直到到达树左面的最底部,再开始处理节点(也就是在把节点的数值放进result数组中),这就造成了处理顺序和访问顺序是不一致的。
那么在使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
stack<TreeNode*>st;
vector<int>result;
TreeNode* cur=root;
while(cur!=NULL||!st.empty())
{
if(cur!=NULL)
{
st.push(cur);
cur=cur->left;//左
}
else
{//如果当前节点为空,就取出栈顶元素,再查看栈顶元素是否有右子树
cur=st.top();
st.pop();
result.push_back(cur->val);//中
cur=cur->right;
}
}
return result;
}
};
统一迭代法(暂缺,有空补)
层序遍历
题目:102. 二叉树的层序遍历 - 力扣(LeetCode)
1.借助队列法
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;
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;
}
};
二维数组每一维记录每一层树节点。上一层树节点出列入数组的同时下一层树节点也要入列。
2.递归
class Solution {
public:
void order(TreeNode* cur, vector<vector<int>>& result, int depth)
{
if (cur == nullptr) return;
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;
}
};
掌握层序遍历后可以一打十的相似题目
题目:107. 二叉树的层序遍历 II - 力扣(LeetCode)
和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. 二叉树的右视图 - 力扣(LeetCode)
不同点,层序遍历时,把每一层的最后一个元素放进数组就行
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. 二叉树的层平均值 - 力扣(LeetCode)
多设置一个sum来统计每层的总和就行。最后把平均值存入数组。
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+=node->val;//计算总和
if(node->left)que.push(node->left);
if(node->right)que.push(node->right);
}
result.push_back(sum/size);//平均值
}
return result;
}
};
题目:429. N 叉树的层序遍历 - 力扣(LeetCode)
因为结点的孩子是不固定多少个的,所以遍历的小区别就是需要通过树指针来知道结点孩子数。
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();
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])//第i个孩子不为空就入列
que.push(node->children[i]);
}
}
result.push_back(vec);
}
return result;
}
};
题目:515. 在每个树行中找最大值 - 力扣(LeetCode)
设置一个max值,每一层扫描时,谁大谁就入最大值数组。
class Solution {
public:
vector<int> largestValues(TreeNode* root) {
queue<TreeNode*> que;
if (root != NULL) que.push(root);
vector<int> result;
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. 填充每个节点的下一个右侧节点指针 - 力扣(LeetCode)
两个指针,nodepre指向每层的头结点,从第二个开始,node指向当前结点,然后上一个节点指向下一个节点,更新nodepre的位置,知道指向null。
class Solution {
public:
Node* connect(Node* root) {
queue<Node*> que;
if (root != NULL) 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();//node指向当前结点
que.pop();
prenode->next=node;//前一个结点指向当前节点
prenode=prenode->next;//更新pre位置
}
if(node->left)que.push(node->left);
if(node->right)que.push(node->right);
}
prenode->next=NULL;//每层最后一个结点指向空
}
return root;
}
};
题目117. 填充每个节点的下一个右侧节点指针 II - 力扣(LeetCode)
和上题一模一样。
题目:104. 二叉树的最大深度 - 力扣(LeetCode)
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
迭代法,层数就是最大深度,比较简单
class Solution {
public:
int maxDepth(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);
}
}
return depth;
}
};
题目:111. 二叉树的最小深度 - 力扣(LeetCode)
和上一题差不多,区别只是部分代码。
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->right&&!node->left)return depth;//只要左右子树都为空了,那么就可以找到最低深度的了
}
}
return depth;
}
};