数据结构(六)—— 二叉树(7)构建二叉树

news2024/11/18 4:38:38

文章目录

  • 如何使用递归构建二叉树
    • 1、创建一颗全新树(题1-5)
    • 2、在原有的树上新增东西(题6)
  • 1 106 从 后序 与 中序 遍历序列构造二叉树
  • 2 105 从 前序 与 中序 遍历序列构造二叉树
  • 3 108 将有序数组转换为二叉搜索树(输入)
  • 4 654 最大二叉树(输入很难想)
    • 递归
    • 单调栈
  • 5 617 合并二叉树
  • 6 701 二叉搜索树中的插入操作(重点独立重做)
  • 7 450 删除二叉搜索树中的节点(中等题)
  • 8 538 把二叉搜索树转换为累加树


如何使用递归构建二叉树

1、创建一颗全新树(题1-5)

构造树一般采用前序遍历,因为先构造中间节点,然后递归构造左子树和右子树。

TreeNode* newtree = new TreeNode(val);   // 每次递归都new一个节点
if(..) return newtree;      // 直接返回这个root
newtree->left = 递归函数(....)    // 用这个新建节点的左去接递归函数的返回值
newtree->right = 递归函数(....) 
return newtree;

2、在原有的树上新增东西(题6)

删除二叉树节点,增加二叉树节点,用递归函数的返回值来完成。

输入为root
if (root == nullptr) {
    TreeNode* temp = new TreeNode(val);   // 在树上新加的节点
    return temp;
}

root->left = insertIntoBST(root->left, val);
root->right = insertIntoBST(root->right, val);

return root;  // return输入的root

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

1和2为同一类型,题外话:
前序和中序可以唯一确定一棵二叉树。
后序和中序可以唯一确定一棵二叉树。

前序和后序不能唯一确定一棵二叉树!因为没有中序遍历无法确定左右部分,也就是无法分割。

设有一颗二叉树:
在这里插入图片描述

树的遍历结果可得两个规律:
1、在后序遍历序列中,最后一个元素为树的根节点
2、在中序遍历序列中,根节点的左边为左子树,根节点的右边为右子树
在这里插入图片描述

根据特性还原,步骤如下

1、输入中序和后序(如果是空数组就退)
2、处理后序:找到根节点(后序最后一个元素)(如果后序只有一个元素了直接返回)
3、处理中序:中序中根节点的位置
4、将中序分为左、右子树
5、得到后序的左、右子树
6、子树作为下一次递归的输入

步骤拆解伪代码如下:

// 第一步
if (postorder.size() == 0) return NULL;

// 第二步:后序遍历数组最后一个元素,就是当前的中间节点
int rootValue = postorder[postorder.size() - 1];
TreeNode* root = new TreeNode(rootValue);
// 叶子节点
if (postorder.size() == 1) return root;

// 第三步:确认根节点在中序中的位置
int delimiterIndex;
for (delimiterIndex = 0; delimiterIndex < inorder.size(); delimiterIndex++) {
   if (inorder[delimiterIndex] == rootValue) break;
}

// 第四步:切割中序数组,得到 中序左数组和中序右数组
// 第五步:切割后序数组,得到 后序左数组和后序右数组

// 第六步
root->left = traversal(中序左数组, 后序左数组);
root->right = traversal(中序右数组, 后序右数组);

在这里插入图片描述
第4和第5尤其重要,如何将输入的中序和后序分割为中序左右树和后序左右树尤为关键

4、将中序分割左右子树

// 找到根节点在中序中的位置(中序的切割点)
int delimiterIndex;
for (delimiterIndex = 0; delimiterIndex < inorder.size(); delimiterIndex++) {
    if (inorder[delimiterIndex] == rootValue) break;
}

// 左闭右开区间:[0, delimiterIndex)
vector<int> leftInorder(inorder.begin(), inorder.begin() + delimiterIndex);
// [delimiterIndex + 1, end)  注意这里加1,因为要跳过根节点,下面分割后序不加。
vector<int> rightInorder(inorder.begin() + delimiterIndex + 1, inorder.end());

5、后序数组的切割点怎么找?
后序的根节点在最后,不像中序可以靠根节点来分割左右子树。
此时有一个必然条件,中序数组大小一定是和后序数组的大小相同的,后序数组就可以按照左中序数组的大小来切割,切成左后序数组和右后序数组。

// postorder 舍弃末尾元素,因为这个元素就是中间节点,已经用过了
postorder.resize(postorder.size() - 1);

// 左闭右开,注意这里使用了左中序数组大小作为切割点:[0, leftInorder.size)
vector<int> leftPostorder(postorder.begin(), postorder.begin() + leftInorder.size());
// [leftInorder.size(), end)   注意这里不加1,前面分割中序时加
vector<int> rightPostorder(postorder.begin() + leftInorder.size(), postorder.end());

整体代码如下

TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
    int sizeinorder = inorder.size();
    int sizepostorder = postorder.size();

    if (sizepostorder == 0) return nullptr;

    int rootValue = postorder[sizepostorder - 1];  // 根节点的值
    TreeNode* newtree = new TreeNode(rootValue);
    if (sizepostorder == 1) return newtree;

    int delimiterIndex;
    for (delimiterIndex = 0; delimiterIndex < sizeinorder; delimiterIndex++) {
        if (inorder[delimiterIndex] == rootValue) break;
    }

    vector<int> inorderleft(inorder.begin(), inorder.begin() + delimiterIndex);
    vector<int> inorderright(inorder.begin() + delimiterIndex + 1, inorder.end());

    vector<int> postorderleft(postorder.begin(), postorder.begin() + inorderleft.size());
    vector<int> postorderright(postorder.begin() + inorderleft.size(), postorder.end() - 1);

    newtree->left = buildTree(inorderleft, postorderleft);
    newtree->right = buildTree(inorderright, postorderright);

    return newtree;
}

2 105 从 前序 与 中序 遍历序列构造二叉树

注意看分割时的索引

TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
    int sizepreorder = preorder.size();
    int sizeinorder = inorder.size();

    if (sizepreorder == 0) return nullptr;

    int rootval = preorder[0];
    TreeNode* newtree = new TreeNode(rootval);
    if (sizepreorder == 1) return root;

    int inrootindex;
    for (inrootindex = 0; inrootindex < sizeinorder; ++inrootindex) {
        if (inorder[inrootindex] == rootval) break;
    }

    vector<int> inorderleft(inorder.begin(), inorder.begin() + inrootindex);
    vector<int> inorderright(inorder.begin() + inrootindex + 1, inorder.end());

    vector<int> preorderleft(preorder.begin() + 1, preorder.begin() + 1 + inorderleft.size());
    vector<int> preorderright(preorder.begin() + 1 + inorderleft.size(), preorder.end());

    newtree->left = buildTree(preorderleft, inorderleft);
    newtree->right = buildTree(preorderright, inorderright);

    return newtree;
}

3 108 将有序数组转换为二叉搜索树(输入)

二分法复习,数组的mid就是根节点
1、输入输出
TreeNode* construct(vector<int>& nums, int left, int right)
输入一个数组nums,返回一个从nums[left]nums[right]的元素构建一棵树
2、左指针大于右指针退出递归
if (left > right) return nullptr;
3、使用二分法找到这一区间[left, right]中的中间值mid,记为nums[mid],从而确定根节点的值。(本质就是寻找分割点,分割点作为当前节点,然后递归左区间和右区间)

问题:数组长度为偶数,中间节点有两个,取哪一个?实际上取哪一个都可以,答案都对。
int mid = (left + right) / 2;,这么写其实有一个问题,就是数值越界,例如left和right都是最大int,这么操作就越界了,在二分法中尤其需要注意!求mid写做:int mid = left + ((right - left) / 2);

int mid = left + ((right - left) / 2);
TreeNode* newtree = new TreeNode(nums[mid]);
newtree->left = traversal(nums, left, mid - 1);
newtree->right = traversal(nums, mid + 1, right);
return newtree;

4、整合

class Solution {
private:
    TreeNode* traversal(vector<int>& nums, int left, int right) {
        if (left > right) return nullptr;
        int mid = left + ((right - left) / 2);
        TreeNode* root = new TreeNode(nums[mid]);
        root->left = traversal(nums, left, mid - 1);
        root->right = traversal(nums, mid + 1, right);
        return root;
    }
    
public:
    TreeNode* sortedArrayToBST(vector<int>& nums) {
        TreeNode* root = traversal(nums, 0, nums.size() - 1);
        return root;
    }
};

4 654 最大二叉树(输入很难想)

递归

构造树一般采用前序遍历,因为先构造中间节点,然后递归构造左子树和右子树。

1、输入输出
TreeNode* construct(vector<int>& nums, int left, int right)
输入一个数组nums,返回一个从nums[left]nums[right]的元素构建一棵树
2、左指针大于右指针退出递归
if (left > right) return nullptr;
3、找到这一区间[left, right]中的最大值的索引maxindex,记为nums[maxindex],这样确定根节点的值,随后进行递归。

int maxindex = left;
for (int i = left + 1; i <= right; ++i) {
    if (nums[i] > nums[maxindex]) {
        maxindex = i;
    }
}
// 找最大值索引

TreeNode* node = new TreeNode(nums[maxindex]);
node->left = construct(nums, left, maxindex - 1);
node->right = construct(nums, maxindex + 1, right);
return node;

4、整合

class Solution {
public:
    TreeNode* bfs(vector<int>& nums, int left, int right){
        if(left > right){
            return nullptr;
        }

        int maxindex = left;
        for(int i = left + 1; i <= right; ++i){
            if(nums[i] > nums[maxindex]) 
                maxindex = i;
        }

        TreeNode* newtree = new TreeNode(nums[maxindex]);
        newtree->left = bfs(nums, left, maxindex-1);
        newtree->right = bfs(nums, maxindex+1, right);

        return newtree;
    }

    TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
        return bfs(nums, 0, nums.size()-1);
    }
};

单调栈

之后补充

5 617 合并二叉树

TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
    if (root1 == nullptr && root2 == nullptr) return nullptr;

    if (root1 == nullptr) return root2;

    if (root2 == nullptr) return root1;

    TreeNode* newtree = new TreeNode(root1->val + root2->val);
    newtree->left = mergeTrees(root1->left, root2->left);
    newtree->right = mergeTrees(root1->right, root2->right);

    return newtree;
}

6 701 二叉搜索树中的插入操作(重点独立重做)

在原有的树上新增东西

TreeNode* insertIntoBST(TreeNode* root, int val) {
    if (root == nullptr) {
        TreeNode* temp = new TreeNode(val);
        return temp;
    }

    // TreeNode* temp = new TreeNode(root->val);
    if (root->val > val) {
        root->left = insertIntoBST(root->left, val);
    }
    if (root->val < val) {
        root->right = insertIntoBST(root->right, val);
    }

    // return temp;
    return root;
}

7 450 删除二叉搜索树中的节点(中等题)

8 538 把二叉搜索树转换为累加树

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/503133.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

施耐德电气 × 牛客:HR如何助力业务数字化转型?

历经一百八十多年的发展&#xff0c;施耐德电气从一家钢铁企业&#xff0c;进入电力与控制领域&#xff0c;再到如今成为全球能源管理和自动化领域的数字化专家&#xff0c;业务覆盖100多个国家&#xff0c;拥有近13万员工。 其背后离不开HR强大后盾的支撑&#xff0c;下面将独…

Linux文件系统目录有什么用?

学习文件系统的意义在于文件系统有很多设计思路可以迁移到实际的工作场景中&#xff0c;比如&#xff1a; MySQL 的 binlog 和 Redis AOF 都像极了日志文件系统的设计&#xff1b;B Tree用于加速磁盘数据访问的设计&#xff0c;对于索引设计也有通用的意义。 特别是近年来分布…

【Java|golang】1010. 总持续时间可被 60 整除的歌曲

在歌曲列表中&#xff0c;第 i 首歌曲的持续时间为 time[i] 秒。 返回其总持续时间&#xff08;以秒为单位&#xff09;可被 60 整除的歌曲对的数量。形式上&#xff0c;我们希望下标数字 i 和 j 满足 i < j 且有 (time[i] time[j]) % 60 0。 示例 1&#xff1a; 输入&…

【LangChain】如何本地部署基于chatGPT的实时文档和表格数据的助手,在自己的数据上构建chatGPT?

【LangChain】如何本地部署基于chatGPT的实时文档和表格数据的助手&#xff0c;在自己的数据上构建chatGPT 1 相关技术 &#xff08;1&#xff09;LangChain 是一个用于自然语言处理的 Python 库&#xff0c;它的目标是尝试简化自然语言处理任务&#xff0c;提高处理效率和准…

渲染对电脑伤害大吗_如何减少渲染伤机?

虽然说摄影穷三代&#xff0c;但想要自己的本地配置跟上自己的创作速度&#xff0c;高昂的硬件配置支出也可以让自己穷一段时间。CG制作过程中&#xff0c;渲染是必不可少的一步&#xff0c;而且这一步也是很吃“机器”的&#xff0c;那很多人也会担心&#xff0c;如果经常用自…

如何构建产品帮助中心?

据研究表明超过50%的互联网用户&#xff0c;更愿意使用网站中的自助服务支持去解决产品使用中的问题。那么这个时候在线帮助中心&#xff08;即在线帮助页面&#xff09;&#xff0c;就满足了用户这个需求。 在线帮助中心是一个丰富的知识库&#xff0c;可以指导企业的潜在客户…

asp.net+sqlserver漫画绘本借阅管理系统

摘 要1 第1章 系统概述5 1.1 研究背景5 1.2 研究的意义5 1.3 主要研究内容5 第2章 系统开发环境7 2.1 ASP.NET概述7 2.2 动态网站技术介绍8 2.3 数据库技术8 第3章 需求分析9 3.1 需求分析9 3.1.1 功能需求9 3.2 可行性分析9 3.2.1 可行性分析9 3.2.2 技术可行性9 3.2.3 运行可…

OpenCV4 学习指导1 —— Mat:最基本的图像容器

Mat: CV基本的数据结构 1、认识数字图像2、CV的发展历程3、Mat的基本结构3.1、Mat—浅拷贝3.2、Mat—深拷贝 4、颜色的表示方法5、创建Mat对象方法集合5.1、Mat类的常用成员函数5.2、Mat类的成员函数举例 6、Mat的输出样式7、Point 数据存储结构8、参考资源 1、认识数字图像 我…

Yolov8改进---注意力机制:Polarized Self-Attention,效果秒杀CBAM、SE

1. Polarized Self-Attention 论文:https://export.arxiv.org/pdf/2107.00782.pdf Polarized self-attention是一种改进的自注意力机制,旨在提高自注意力模型的效率和准确性。传统的自注意力机制通常需要计算所有位置对所有位置的注意力,这会导致计算复杂度的增加和模型的训…

君子签:助力高校毕业、就业协议电子签,打通就业最后一公里

据介绍&#xff0c;2023届全国普通高校毕业生规模预计达1158万人&#xff0c;同比增加82万人。毕业季即将来临&#xff0c;全国各大高校毕业、就业材料签署压力大&#xff0c;盖章需求激增&#xff0c;如何快捷、高效地处理各类毕业、就业材料签署问题呢&#xff1f; 在教育部…

间隔损失函数

度量损失&#xff1a; Contrastive Loss 核心含义&#xff1a;同类的样本要更近&#xff0c;不同类的样本要更远 正样本不用管&#xff0c;把负样本分开就行&#xff0c;最小化损失函数就是最大化决策边界和负样本的距离 Triplet Loss Triplet Loss是一种用于训练深度学习中…

7-2 哈利·波特的考试

哈利波特要考试了&#xff0c;他需要你的帮助。这门课学的是用魔咒将一种动物变成另一种动物的本事。例如将猫变成老鼠的魔咒是haha&#xff0c;将老鼠变成鱼的魔咒是hehe等等。反方向变化的魔咒就是简单地将原来的魔咒倒过来念&#xff0c;例如ahah可以将老鼠变成猫。另外&…

git的学习2

文章目录 一、Git 创建仓库二、Git 基本操作1总结 一、Git 创建仓库 git init Git 使用 git init 命令来初始化一个 Git 仓库&#xff0c;Git 的很多命令都需要在 Git 的仓库中运行&#xff0c;所以 git init 是使用 Git 的第一个命令。 在执行完成 git init 命令后&#xf…

Opencascad开发(C#)-建模-反向片体(SheetBody)的法向矢量

文章目录 1、前言2、在UG NX中构建一个片体3、在UG NX中查看片体的法向矢量4、采用UFun函数来实现法向反向5、代码实现6、测试效果1、前言 在UG NX中,一张曲面获取其所属的片体(SheetBody)对象,其在构建时有默认的法向矢量,有时处于功能的需求,比如加工时工件的材料去除方…

根据IP获取地理位置信息 — Golang

根据IP获取地理位置信息 — Golang 1 介绍1.1 ip2region1.2 geoip2-golang1.3 总结 2 使用2.1 ip2region2.2 geoip2-golang 1 介绍 1 ip2region 2 geoip2-golang 1.1 ip2region ip2region 是一个离线IP地址定位库和IP定位数据管理框架&#xff0c;10微秒级别的查询效率&…

ipad密码忘了怎么解锁?教你40秒轻松移除!

无论是手机还是平板&#xff0c;相信大部分都会去给它们设上密码&#xff0c;来保证自己的隐私安全。同理&#xff0c;无论是苹果设备还是安卓设备&#xff0c;一旦设了锁屏密码&#xff0c;就会遇到密码忘记的情况&#xff0c;多次输入错误密码还会导致设备被停用。 近日&…

上海亚商投顾:沪指放量上涨创年内新高 中字头个股掀涨停潮

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 市场情绪 沪指今日高开高走&#xff0c;盘中一度涨近2%&#xff0c;站上3400点关口&#xff0c;深成指、创业板指走势较弱。…

openssl一套证书-配置文件和证书签发

dn-param 和 extend 配置文件 CA的dn-param 和 extend CA的dn-param 和 extend CI-csr.cnf 的内容如下&#xff1a; #openssl x509 extfile params extensions extend # This prevent the user to be prompted for values prompt nodistinguished_name dn-param [dn-param…

【C++】-对于自定义类型的输入输出运算符重载

&#x1f496;作者&#xff1a;小树苗渴望变成参天大树 ❤️‍&#x1fa79;作者宣言&#xff1a;认真写好每一篇博客 &#x1f4a8;作者gitee:gitee &#x1f49e;作者专栏&#xff1a;C语言,数据结构初阶,Linux,C 文章目录 前言一、案例引入二、<<的重载三、>>的…

智慧城市3d可视化管理大屏系统有效提高服务质量和效率

随着新一代信息技术飞速融入传统产业&#xff0c;农业数字化、网络化、智能化逐步成为农业现代化发展的基石。实现农业生产环境的智能感知、智能预警、智能决策、智能分析等功能&#xff0c;为农业生产提供精准化保障、高质量运营水平、智能化决策支撑。 3D可视化智慧管理 1&am…