『 C++ 』二叉树进阶OJ题(下)

简介: 『 C++ 』二叉树进阶OJ题(下)

『 C++ 』二叉树进阶OJ题(中)https://developer.aliyun.com/article/1424478

从中序遍历与后序遍历序列构造二叉树 🦖

题目链接

🥩 题目描述

给定两个整数数组 inorderpostorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这棵二叉树

示例1:

输入:inorder = [9,3,15,20,7], postorder = [9,15,7,20,3];

输出:[3,9,20,null,null,15,7];

从题目描述中可以看出该题与上一题的思路如出一辙;


🥩 解题思路

该题的思路与[从前序与中序遍历序列构造二叉树]十分相似,唯一不同的是一个给的是前序遍历序列,一个给的是后序遍历序列;

其思路也大致相同,即采用中序遍历序列来判断左右子区间,用后序遍历序列来判断根节点;


🥩 代码

class Solution {
public:
         TreeNode* _buildTree(vector<int>& inorder, vector<int>& postorder,int& cur,int inbegin,int inend){
         if(inbegin>inend) return nullptr;
         TreeNode* newnode = new TreeNode(postorder[cur]);
         int mid = inbegin;
         while(mid<=inend){ 
            if(inorder[mid] == postorder[cur]) break;    
            ++mid;
         }
         --cur;
         newnode->right = _buildTree(inorder,postorder,cur,mid+1,inend);
         newnode->left = _buildTree(inorder,postorder,cur,inbegin,mid-1);
         return newnode;
    }
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        int cur = postorder.size() - 1;
        return _buildTree(inorder, postorder, cur, 0, inorder.size() - 1);
    }
};

二叉树的前序遍历(非递归迭代) 🦖

题目链接

🥩 题目描述

给定一个二叉树的根节点root , 返回它节点值的 前序 遍历;

示例1:

输入 : root = [ 1 , null , 2 , 3 ];

输出:[ 1 , 2 , 3 ];


🥩 解题思路

利用C++完成这道题时需要返回一个vector;

该题以递归的思路来做的话会较为简单,根据每次递归子树时将会遇到的情况做特殊处理并将其放到一个vector容器当中并返回;

而若是非递归的话则不能使用递归的这种思路;

但是使用非递归的话可以利用其他的容器对树中的节点做处理;

就以该题前序遍历为例;

前序遍历的路径(子树访问的顺序)为根,左子树,右子树;

这里不以示例1为例,以该图为例;

这棵树以前序遍历最终得到的结果为[ 10 , 6 , 4 , 8 , 14 , 12 , 16 ] ;

即其可以看成左路节点左路节点右子树的左路节点,将问题化为子问题;

以该图为例即为将一棵树分为左路节点以及左路节点的右子树;

将问题划分为子问题;

可以使用一个stack容器(栈),根据其LIFO的特性将数据进行存储,并且反向拿出,即以该树的左路节点为例,将左路节点全部入栈,当栈顶左路节点被出时访问它的右子树;

该种方式不是递归但是思路上胜似递归;


🥩 代码

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        stack<TreeNode*> st;//用来存放左路节点
        vector<int> ret;//用来做返回
        TreeNode *cur = root;
        while(!st.empty() || cur){
            while(cur){
            //节点必定存在,要么在栈中要么为cur所在位置
            //故当栈不为空或者cur不为nullptr时将其入队列
            st.push(cur);
            //由于该遍历方式为前序遍历即 (根,左子树,右子树) 故该节点可以直接访问
            ret.push_back(cur->val);
            cur = cur->left;//遍历左路节点
            }
            //每次去访问栈顶元素(左路节点)的右子树
            TreeNode*top = st.top();
            st.pop();
            cur = top->right;
        }
        return ret;
    }
};

二叉树的中序遍历(非递归迭代) 🦖

题目链接

🥩 题目描述

给定一个二叉树的根节点root ,返回它的中序遍历;

示例1:

输入 : root = [ 1 , null , 2 , 3 ];

输出:[ 1 , 3 , 2 ];


🥩 解题思路

该题的解题思路与上一题二叉树的中序遍历如出一辙,即也是通过将树分为左路节点与左路节点的右子树的方式;

但稍微不同的是,对于该题来说由于是以中序遍历即(左子树,根,右子树)的方式对树进行遍历,所以第一次对左路节点进行遍历时不能直接进行访问,当左子树访问完后才能访问这个子树的根节点(左路节点);

大体上还是相同;


🥩 代码

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> ret;//作为函数返回
        stack<TreeNode*>st;//用来访问左路节点及节点的右子树
        TreeNode*cur = root;//该指针用来遍历左路节点
        while(cur || !st.empty()){//节点必定存在,要么在当前cur,要么在栈中,否则即为遍历结束
            while(cur){
                st.push(cur);
                //将节点入栈,且在入栈时不能直接进行访问,当该节点的左子树被访问完毕才能访问该节点
                cur = cur->left;
            }
            TreeNode* top = st.top();
            st.pop();
            //这个节点必定是这棵树(子树)的左路节点,根据中序遍历(左子树 根 右子树)的顺序可以访问该节点
            ret.push_back(top->val);
            //访问该左路节点的右子树
            cur = top->right;
        }
        return ret;
    }
};

二叉树的后序遍历(非递归迭代) 🦖

题目链接

🥩 题目描述

给你一棵二叉树的根节点 root ,返回其节点值的 后序遍历

示例1:

输入:root = [1,null,2,3]

输出:[3,2,1]


🥩 解题思路

思路1:

思路1的思路即为将前序遍历进行修改,将其修改为==(根,右子树,左子树)的访问顺序==,再最后对结果进行一次逆置,即:后序遍历(左子树,右子树,根);

这种方式的思路是可行的,但是若是需要边遍历边进行打印则不可取;

该种方法本质上是一种取巧的办法,但是在该题中可以运行;


思路2:

以类似于前序与中序遍历的思路相同,但是唯一不同的是在前序遍历或者中序遍历时,前序遍历时根节点可以直接访问,而中序遍历时节点的左子树访问完毕后即可以访问根节点;

而后序遍历与前序中序不同的是后序遍历必须将节点的左右子树都访问结束后才能访问根节点;

由于也是按照左路节点与左路节点的右子树的左路节点将问题化为子问题进行迭代的思路,所以左路节点的左子树可以默认为已经访问完毕,此时只需要处理节点的右子树即可;

当节点的右子树为nullptr时为了避免对空指针的非法解引用操作,所以当该节点的右子树为空时空间直接访问该节点;

当该节点的右子树访问完毕时也可以访问该节点,那么问题是如何判断该节点的右子树是否被访问完毕?

可以使用一个prev指针来记录每次所访问节点的位置,并将该位置记住,当在栈顶出取出节点时,如果该节点的右子树为空或者是该节点的右子树等于上次访问过的节点(即prev)时表示该节点的右子树已经被访问,可以直接访问该节点;


🥩 代码

思路1:

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        stack<TreeNode*> st;//用来存放右路节点
        vector<int> ret;//用来作返回
        TreeNode *cur = root;//用于遍历
        while(!st.empty() || cur){
            while(cur){
            //节点必定存在,要么在栈中要么为cur所在位置
            //故当栈不为空或者cur不为nullptr时将其入队列
            st.push(cur);
            //由于该遍历方式为变相的前序遍历 即 (根,右子树,左子树) 故该节点可以直接访问
            ret.push_back(cur->val);
            cur = cur->right;//遍历右路节点
            }
            //每次去访问栈顶元素(右路节点)的左子树
            TreeNode*top = st.top();
            st.pop();
            cur = top->left;
        }
        //得出的结果即为 (根,右子树,左子树),逆置后即为 (左子树,右子树,根)即为后序遍历顺序;
        reverse(ret.begin(),ret.end());
        return ret;
    }
};

该思路为前序遍历的修改并采用逆置得到后序遍历的结果;


思路2:

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        stack<TreeNode*> st;//用来存放左路节点
        vector<int> ret;//用来做返回
        TreeNode *cur = root;//遍历左路节点
        TreeNode*prev = nullptr; //用来记录每个被访问的节点
        while(!st.empty() || cur){
            while(cur){
                //遍历左路节点,这里遍历左路节点时只对左路节点进行入栈操作
                //由于是后序遍历所以第一次访问根节点的时候不能进行访问
                st.push(cur);
                cur = cur->left;
            }
            TreeNode*top = st.top();//取出栈顶节点并准备访问栈顶节点的右子树(左子树默认已经访问完毕)
            if(top->right == nullptr || prev == top->right){
                //当右子树为空时为了避免对空指针的非法解引用且没必要再对右子树进行访问
                //由于prev指针记录了每次上一次访问的节点,所以当prev == 该节点的右子树时则表示该节点的右子树已经被访问完毕
                //可以直接访问该节点
                ret.push_back(top->val);
                st.pop();
                prev = top;
            }
            else{
                //表示该节点的右子树未被访问过,需要先访问该节点的右子树
                cur = top->right;
            }
        }
        return ret;//返回结果
    }
};


相关文章
|
4月前
|
存储 C++
【C++】二叉树进阶之二叉搜索树(下)
【C++】二叉树进阶之二叉搜索树(下)
32 4
|
4月前
|
Java 编译器 C++
【C++】二叉树进阶之二叉搜索树(上)
【C++】二叉树进阶之二叉搜索树(上)
36 3
|
4月前
|
算法 C++
【C++高阶】高效搜索的秘密:深入解析搜索二叉树
【C++高阶】高效搜索的秘密:深入解析搜索二叉树
42 2
|
5月前
|
编译器 C++
C++模板进阶
C++模板进阶
24 1
|
5月前
|
存储 算法 程序员
【C++进阶】深入STL之 栈与队列:数据结构探索之旅
【C++进阶】深入STL之 栈与队列:数据结构探索之旅
56 4
|
5月前
|
算法 安全 编译器
【C++进阶】模板进阶与仿函数:C++编程中的泛型与函数式编程思想
【C++进阶】模板进阶与仿函数:C++编程中的泛型与函数式编程思想
50 1
|
5月前
|
存储 缓存 编译器
【C++进阶】深入STL之list:模拟实现深入理解List与迭代器
【C++进阶】深入STL之list:模拟实现深入理解List与迭代器
36 0
|
5月前
|
C++ 容器
【C++进阶】深入STL之list:高效双向链表的使用技巧
【C++进阶】深入STL之list:高效双向链表的使用技巧
59 0
|
5月前
|
编译器 C++ 容器
【C++进阶】深入STL之vector:深入研究迭代器失效及拷贝问题
【C++进阶】深入STL之vector:深入研究迭代器失效及拷贝问题
161 0
|
7天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
33 4