二叉树专题

本蒻苟发文,有任何不足的地方欢迎大佬们斧正~(^∀^●)ノシ
一、构建二叉树
俗话说的好,算法重中之重就是数据存储结构,所以我们先来学一下如何通过给出 中序+前序遍历 或 中序+后序遍历 来构建二叉树
为什么没有 前序+后序遍历 构建二叉树呢?因为这样构建不了二叉树,或者说不能构建唯一的一个二叉树
① 在中序遍历中对根节点快速定位
这是两种构建二叉树方法的桥接点,非常重要。一种简单的方法是直接扫描整个中序遍历的结果并找出根节点,但这样做的时间复杂度较高。
我们可以考虑使用哈希表来帮助我们快速地定位根节点。对于哈希映射中的每个键值对,键(key)表示节点的值,值(value)表示其在中序遍历中的出现位置。这样在中序遍历中查找根节点位置的操作,只需要 O(1)的时间
vector<int> in;
... //假设已经读入中序遍历的结果
unordered_map<int, int> pos;
for(int i = 0; i < in.size(); i++)
pos[in[i]] = i;
② 前序 + 中序 遍历构建二叉树
👉练习题:点这里
#include <bits/stdc++.h>
using namespace std;
int n;
vector<int> pre, in;
unordered_map<int, int> pos;
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
TreeNode* buildTree(int il, int ir, int pl, int pr) {
if(il > ir) return nullptr;
int k = pos[pre[pl]]; //根节点的位置
TreeNode* root = new TreeNode(pre[pl]);
//中序遍历中「从 左边界 开始到 根节点定位-1」的元素就对应了
//先序遍历中「从 左边界+1 开始的 左边界+左子树节点数目」的元素
root->left = buildTree(il, k-1, pl+1, pl+(k-il));
//中序遍历中「从 根节点定位+1 到 右边界」的元素就对应了
//先序遍历「从 左边界+1+左子树节点数目 开始到 右边界」的元素
root->right = buildTree(k+1, ir, pl+1+(k-il), pr);
return root;
}
int main(){
int num;
cin >> n;
for(int i = 0; i < n; i++){
scanf("%d", &num);
in.push_back(num);
}
for(int i = 0; i < n; i++){
scanf("%d", &num);
pre.push_back(num);
}
for(int i = 0; i < in.size(); i++)
pos[in[i]] = i;
TreeNode* root = buildTree(0, n-1, 0, n-1);
return 0;
}
③ 后序 + 中序 遍历构建二叉树
👉练习题:点这里
#include <bits/stdc++.h>
using namespace std;
int n;
vector<int> in, post;
vector<vector<int>> tree;
unordered_map<int, int> pos;
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
TreeNode* buildTree(int il, int ir, int pl, int pr) {
if(il > ir) return nullptr;
int k = pos[post[pr]];
TreeNode* root = new TreeNode(post[pr]);
//中序遍历中「从 左边界 开始到 根节点位置-1」的元素就对应了
//后序遍历中「从 左边界 开始到 左边界+左子树节点数目」的元素
root->left = buildTree(il, k-1, pl, pl+(k-1-il));
//中序遍历中「从 根节点定位+1 到 右边界」的元素就对应了
//后序遍历「从 左边界+左子树节点数目+1 开始到 右边界-1」的元素
root->right = buildTree(k+1, ir, pl+(k-1-il)+1, pr-1);
return root;
}
int main(){
int num;
cin >> n;
for(int i = 0; i < n; i++){
scanf("%d", &num);
post.push_back(num);
}
for(int i = 0; i < n; i++){
scanf("%d", &num);
in.push_back(num);
}
for(int i = 0; i < in.size(); i++)
pos[in[i]] = i;
TreeNode* root = buildTree(0, n-1, 0, n-1);
return 0;
}
二、前中后序 遍历二叉树
① 前序遍历二叉树 (非递归)
使用栈来模拟递归的操作,循环条件:节点不为NULL,且栈不为空。
1. 如果当前节点不为空,访问节点,并且把节点进栈,节点指向其左孩子,直至左孩子为空
2. 这时相当于左子树已经遍历完了,我们需要访问右节点,将当前元素指向栈顶元素右孩子,弹出栈顶元素
vector<int> preorder(TreeNode* root) {
vector<int> res;
stack<TreeNode*> st;
TreeNode* cur = root;
while(cur || st.size())
{
while(cur)
{
res.push_back(cur->val); //第一次进栈时访问节点
st.push(cur);
cur = cur->left;
}
TreeNode* p = st.top();
st.pop();
cur = p->right;
}
return res;
}
② 中序遍历二叉树 (非递归)
和前序遍历类似,只不过访问节点从第一次进栈时变成出栈时访问节点
vector<int> inorder(TreeNode* root) {
vector<int> res;
stack<TreeNode*> st;
TreeNode* cur = root;
while(cur || st.size())
{
while(cur)
{
st.push(cur);
cur = cur->left;
}
TreeNode* p = st.top();
st.pop();
res.push_back(p->val); //变成出栈时访问节点
cur = p->right;
}
return res;
}
③ 后序遍历二叉树 (非递归)
后序遍历的难点在于第一次将根节点出栈后,并不能直接访问,必须访问其右子树后才可以。非递归后序遍历有很多种实现方式,我个人比较喜欢使用pair来标记栈中元素的状态,状态为false时不可访问,状态为true时才能
vector<int> postorder(TreeNode* root) {
vector<int> res;
stack<pair<TreeNode*, bool>> s;
s.push({root, false});
bool visited;
while (s.size())
{
root = s.top().first;
visited = s.top().second;
s.pop();
if (!root) continue;
if (visited) res.push_back(root->val);
else {
s.push({root, true});
s.push({root->right, false});
s.push({root->left, false});
}
}
return res;
}
三、变种题 之 二叉树
BFS_198">① BFS层次遍历二叉树
实现思路: 二叉树的层次遍历通常情况下借助于队列实现。当根节点不为空时,将根节点加入队列。当队列不为空的时候,访问队列头元素,同时将队头元素的非空子节点进队列,将队头元素出队
vector<int> levelTraversal(TreeNode* root) {
vector<int> res;
queue<TreeNode*> q;
if(!root) return res;
q.push(root);
while(!q.empty())
{
TreeNode* cur = q.front();
q.pop();
res.push_back(cur->val);
if(cur—>left) q.push(cur->left);
if(cur->right) q.push(cur->right);
}
return res;
}
② DFS递归遍历二叉树
有时候题目只需要得到某一层的最大值,最小值,第一个节点,最后一个节点,左右视图之类的,我们就没有必要严格按照层次遍历的顺序了。可以使用dfs来遍历每一层的节点。注意:这不是真正的层次遍历
vector<vector<int>> tree;
void dfs(TreeNode* root, int level){
if(!root) return;
if(tree.size() == level)
tree.push_back({});
tree[level].push_back(root->val);
dfs(root->left, level + 1);
dfs(root->right, level + 1);
}
③ 翻转二叉树
也可以看作是二叉树的镜面,即将非叶子节点的左右孩子对换
void dfs(TreeNode* root) {
if(!root) return;
swap(root->left, root->right);
dfs(root->left);
dfs(root->right);
}