二叉树理论基础
- 二叉树节点的定义:
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
- Morris遍历是二叉树遍历算法的超强进阶算法,可以将非递归遍历中的空间复杂度降为O(1),感兴趣可以学习
二叉树的递归遍历
- 递归三部曲:
- 确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
- 确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
- 确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
- 好了,我们确认了递归的三要素,接下来就来练练手:
以下以前序遍历为例:
- 确定递归函数的参数和返回值:因为要打印出前序遍历节点的数值,所以参数里需要传入vector来放节点的数值,除了这一点就不需要再处理什么数据了也不需要有返回值,所以递归函数返回类型就是void,代码如下:
void traversal(TreeNode* cur, vector<int>& vec)
- 确定终止条件:在递归的过程中,如何算是递归结束了呢,当然是当前遍历的节点是空了,那么本层递归就要结束了,所以如果当前遍历的这个节点是空,就直接return,代码如下:
if (cur == NULL) return;
- 确定单层递归的逻辑:前序遍历是中左右的循序,所以在单层递归的逻辑,是要先取中节点的数值,代码如下:
vec.push_back(cur->val); // 中
traversal(cur->left, vec); // 左
traversal(cur->right, vec); // 右
单层递归的逻辑就是按照中左右的顺序来处理的,这样二叉树的前序遍历,基本就写完了,再看一下完整代码:
前序遍历:
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;
}
};
- 中序和后序遍历不再赘述
- 144.二叉树的前序遍历
- 145.二叉树的后序遍历
- 94.二叉树的中序遍历
二叉树的迭代遍历
前序遍历
- 中左右,所以先输出节点的值,入栈时先右后左才能保证正确的次序
vector<int> preorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> res;
if(root) st.push(root);
while(!st.empty()){
TreeNode* node=st.top();
st.pop();
res.push_back(node->val);
if(node->right) st.push(node->right); // 注意这里
if(node->left) st.push(node->left);
}
return res;
}
后序遍历
- 左右中的次序,反过来就是中右左,只需稍微修改下前序的模板再反转即可
vector<int> postorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> res;
if(root) st.push(root);
while(!st.empty()){
TreeNode* node=st.top();
st.pop();
res.push_back(node->val);
if(node->left) st.push(node->left); // 注意这里
if(node->right) st.push(node->right);
}
reverse(res.begin(), res.end());
return res;
}
中序遍历
- 中序不能通用前序的模板,因为左中右的次序会导致中节点被访问两次的情况,所以需要当前指针记录最左侧的节点,防止重复访问
vector<int> inorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> res;
TreeNode* node=root; // 需要一个指针记录当前访问的节点
while(node || !st.empty()) // 注意条件多了当前指针
if(node){
st.push(node);
node=node->left; // 一直到最左侧
} else{
node=st.top(); // 当前访问的是栈顶节点
st.pop();
res.push_back(node->val);
node=node->right;
}
return res;
}
N叉树的前序遍历和后序遍历
vector<int> preorder(Node* root) {
vector<int> res;
stack<Node*> st;
if(root) st.push(root);
while(!st.empty()){
Node* node=st.top();
st.pop();
res.push_back(node->val);
// 只有这儿发生了改变
for(int i=node->children.size()-1; i>=0; i--)
if(node->children[i]) st.push(node->children[i]);
}
return res;
}
- 后序:
vector<int> postorder(Node* root) {
vector<int> res;
stack<Node*> st;
if(root) st.push(root);
while(!st.empty()){
Node* node=st.top();
st.pop();
res.push_back(node->val);
// 只有这儿发生了改变
for(auto child : node->children)
if(child) st.push(child);
}
reverse(res.begin(), res.end());
return res;
}
二叉树的统一迭代法
- 以中序遍历为例,感觉效率不是很高()
vector<int> inorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> res;
if(root) st.push(root);
while(!st.empty()){
TreeNode* node=st.top();
if(node){
st.pop(); // 将该节点弹出,避免重复操作,将右中左节点添加到栈中
if(node->right) st.push(node->right);// 添加右节点
st.push(node); // 添加中节点
st.push(nullptr); // 中节点访问过,但还没有处理,加入空节点做为标记
if(node->left) st.push(node->left);
} else{
st.pop(); // 将空节点弹出
node=st.top();
st.pop();
res.push_back(node->val);
}
}
return res;
}
二叉树的层序遍历
- 掌握一个模板,怒刷十题!
102.二叉树的层序遍历
vector<vector<int>> levelOrder(TreeNode* root) {
queue<TreeNode*> q;
vector<vector<int>> res;
if(root==nullptr) return res;
q.push(root);
while(!q.empty()){
int size=q.size();
vector<int> vec;
for(int i=0; i<size; i++){
TreeNode* node=q.front();
q.pop();
vec.push_back(node->val);
if(node->left) q.push(node->left);
if(node->right) q.push(node->right);
}
res.push_back(vec);
}
return res;
}
107.二叉树的层次遍历II
- 反转上题的vector即可
199.二叉树的右视图
vector<int> rightSideView(TreeNode* root) {
queue<TreeNode*> q;
vector<int> res;
if(root) q.push(root);
while(!q.empty()){
int size=q.size();
TreeNode* node=nullptr;
while(size--){
node=q.front();
q.pop();
if(size==0) res.push_back(node->val);
if(node->left) q.push(node->left);
if(node->right) q.push(node->right);
}
}
return res;
}
637.二叉树的层平均值
- 每层求和再除以层节点数
429.N叉树的层序遍历
vector<vector<int>> levelOrder(Node* root) {
queue<Node*> q;
vector<vector<int>> res;
if(root) q.push(root);
while(!q.empty()){
int size=q.size();
vector<int> vec;
for(int i=0; i<size; i++){
Node* node=q.front();
vec.push_back(node->val);
q.pop();
for(auto child : node->children)
if(child) q.push(child);
}
res.push_back(vec);
}
return res;
}
515.在每个树行中找最大值
- 注意要使用定义的INT_MIN才行,有+0和-0的错误
116.填充每个节点的下一个右侧节点指针
Node* connect(Node* root) {
queue<Node*> q;
if(root) q.push(root);
while(!q.empty()){
int size=q.size();
for(int i=0; i<size; i++){
Node* node=q.front();
q.pop();
if(i!=size-1) node->next=q.front();
if(node->left) q.push(node->left);
if(node->right) q.push(node->right);
}
}
return root;
}
117.填充每个节点的下一个右侧节点指针II
- 和上题没什么区别
104.二叉树的最大深度
int maxDepth(TreeNode* root) {
queue<TreeNode*> q;
int dep=0;
if(root) q.push(root);
while(!q.empty()){
dep++;
int size=q.size();
for(int i=0; i<size; i++){
TreeNode* node=q.front();
q.pop();
if(node->left) q.push(node->left);
if(node->right) q.push(node->right);
}
}
return dep;
}
111.二叉树的最小深度
- 增加一行就好了
if(!node->left&&!node->right) return dep;
翻转二叉树
- 力扣题目链接
- 本质上是每个分支节点的左右孩子都反转两次
- 递归法:
TreeNode* invertTree(TreeNode* root) {
if(root==nullptr) return root;
swap(root->left, root->right);
invertTree(root->left);
invertTree(root->right);
return root;
}
- 迭代法(任意遍历次序,除中序麻烦):
TreeNode* invertTree(TreeNode* root) {
if(root==nullptr) return root;
stack<TreeNode*> st;
st.push(root);
while(!st.empty()){
TreeNode* node=st.top();
st.pop();
swap(node->left, node->right);
if(node->right) st.push(node->right);
if(node->left) st.push(node->left);
}
return root;
}
参考资料
[1] 代码随想录
[2] Leetcode题解