【算法竞赛模板】二叉树专题(含前中后序遍历建树、DFS与BFS等对二叉树的操作)

  本蒻苟发文,有任何不足的地方欢迎大佬们斧正~(^∀^●)ノシ

一、构建二叉树

  俗话说的好,算法重中之重就是数据存储结构,所以我们先来学一下如何通过给出 中序+前序遍历中序+后序遍历 来构建二叉树
  为什么没有 前序+后序遍历 构建二叉树呢?因为这样构建不了二叉树,或者说不能构建唯一的一个二叉树

① 在中序遍历中对根节点快速定位

  这是两种构建二叉树方法的桥接点,非常重要。一种简单的方法是直接扫描整个中序遍历的结果并找出根节点,但这样做的时间复杂度较高。
  我们可以考虑使用哈希表来帮助我们快速地定位根节点。对于哈希映射中的每个键值对,键(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层次遍历二叉树

  实现思路: 二叉树的层次遍历通常情况下借助于队列实现。当根节点不为空时,将根节点加入队列。当队列不为空的时候,访问队列头元素,同时将队头元素的非空子节点进队列,将队头元素出队

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);
}
  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ac君

在你们的鼓励下我会多多分享代码

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值