代码随想录算法训练营DAY22|C++二叉树Part.8|235.二叉搜索树的最近公共祖先、450.删除二叉搜索树中的结点

文章目录

  • 235.二叉搜索树的最近公共祖先
    • 思路
    • 伪代码实现
    • CPP代码
    • 迭代法的CPP代码
  • 701.二叉搜索树中的插入操作
    • 思路
    • 伪代码
      • 递归函数有返回值
      • 递归函数不要返回值
      • 迭代方法
    • CPP代码
      • 递归有返回值
      • 递归无返回值
      • 迭代
  • 450.删除二叉搜索树中的结点
    • 思路(分析五种情况)
      • 没找到删除的点
      • 删的点是叶子结点
      • 要删的结点左为空,右为空
      • 要删的结点左不为空,右为空
      • 要删的结点左右都不为空
    • 伪代码实现
    • CPP总体代码
    • 二叉搜索树的迭代法删除结点
    • 普通二叉树的删除方式

235.二叉搜索树的最近公共祖先

力扣题目链接

文章讲解:235.二叉搜索树的最近公共祖先

视频讲解:二叉搜索树找祖先就有点不一样了!| 235. 二叉搜索树的最近公共祖先

状态:跟上一章思路一样文章链接。但是本题中我们不关心遍历顺序,因为BST的特性已经为我们确定好了二叉搜素树的特性。

思路

这个题目如何利用二叉搜索树的特性呢

当我们在遍历根结点的时候,

如果发现根结点比p和q的数值都大的话,说明p和q一定在我们根结点的左子树。所以这时就向左遍历。

如果发现根结点比q和p的数值都小的话,说明目标结点一定在我们根结点的右子树。所以这时就要想右遍历


如果我们的根结点已经到了p和q之间了呢

其实已经说明了该结点就是p和q的最小公共祖先了,因为我们无论再向哪边遍历,都会错过q或者q的。(这是本题中我认为最重要的逻辑)

伪代码实现

  • 递归函数的参数和返回值:

    • 返回值就是我们的公共祖先
    • 传参就是当前结点和p、q
    TreeNode* traversal (cur, p, q){
    }
    
  • 终止条件:其实都不需要这个终止条件,因为题中说了p和q为不同结点且均存在于给定的二叉搜索树中。也就是说一定会找到公共祖先的,所以并不存在遇到空的情况。

  if (cur == NULL) return NULL;
  • 单层递归条件:这里我们根本不关注遍历顺序和中结点的处理逻辑,因为我们的搜索树已经帮我们规定好了搜索路径。
//左
if (cur->val > p->val && cur->val > q->val){	//当前数值比p大,比q大left = traversal(cur->left, p, q);	//说明我们要向左去搜索if (left != NULL) return left; //这里说明我们已经找到公共祖先了
}
//右
if(cur->val < p->val && cur->val < q->val){right = traversal(cur->right, p, q);if (right != NULL) return right;//如果right不为空,说明我们在右子树找到了想要的结果
}
//当前数值在p和q之间
return cur;

CPP代码

class Solution {
private:TreeNode* traversal(TreeNode* cur, TreeNode* p, TreeNode* q) {if (cur == NULL) return cur;// 中if (cur->val > p->val && cur->val > q->val) {   // 左TreeNode* left = traversal(cur->left, p, q);if (left != NULL) {return left;}}if (cur->val < p->val && cur->val < q->val) {   // 右TreeNode* right = traversal(cur->right, p, q);if (right != NULL) {return right;}}return cur;}
public:TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {return traversal(root, p, q);}
};
//精简后的代码
class Solution {
public:TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {if (root->val > p->val && root->val > q->val) {return lowestCommonAncestor(root->left, p, q);} else if (root->val < p->val && root->val < q->val) {return lowestCommonAncestor(root->right, p, q);} else return root;}
};

迭代法的CPP代码

因为搜索的有序性,所以迭代法也很简单

while(cur){ //只要当前结点不为空,我们就一直搜索if (cur->val > p->val && cur->val > q->val)cur = cur->left;if (cur->val < p->val && cur->val < q->val)cur = cur->right;return cur;
}

701.二叉搜索树中的插入操作

力扣题目链接

文章讲解:701.二叉搜索树中的插入操作

视频讲解:原来这么简单? | LeetCode:701.二叉搜索树中的插入操作

状态:我们不管怎么样,直接插到叶子结点,让新插入到元素成为叶子结点即可。那么本题的两个难点显而易见:

  • 怎么找到待插元素应该去的位置
  • 怎么找到叶子结点呢?

思路

其实就是:无论我们插入什么样的结点,总可以在二叉搜索树的叶子结点找到它的位置

为什么我们不改变二叉树的结构,硬在里面插一个呢?那这就把这个题目做复杂了。我们题目并没有这样的要求

伪代码

递归函数有返回值

  • 确定递归函数参数和返回值

    • 参数–根结点和插入的数值
    • 返回值–插入新结点之后,我们这个新二叉树的根结点。
    TreeNode* insert(root, val){}
    
  • 确定终止条件:如果我们的root等于空,说明我们已经找到插入结点的位置了

    • 其中的return node是本段代码的精髓,一定要领会
if (root == NULL){TreeNode* node = new TreeNode(val);return node;//把新插入的结点向上一层返回,因为我们一层层向下递归到叶子结点了,返回给之前的叶子
}
  • 单层递归逻辑
if (val < root->val) root->left = insert(root->left, val); //还记得之前我们返回了新插入的结点吗,就是把他的位置返回给他了
if (val > root->val)root->right = insert(root->right, val);//至此我们就完成了我们的数值在叶子结点对应的位置
return root;

递归函数不要返回值

  • 确定递归函数参数和返回值,这里我要不要返回值,也就是说知道插入的结点位置,直接让其父结点指向插入结点,结束递归
TreeNode* parent; //记录遍历结点的父结点
void traversal(TreeNode* cur, int val)
  • 确定终止条件:既然没有返回值,我们就需要记录上一个结点,遇到空结点了,就让parent左孩子或者右孩子指向新插入的结点。然后结束递归
if (cur == NULL){TreeNode* node = new TreeNode(val);if (parent->val > val) parent->right = node;else parent->left = node;return;
}
  • 确定单层递归逻辑
//让某结点一直跟在cur结点的屁股后面的常用方法
parent = cur;
if (cur->val > val) traversal(cur->left, val);
if (cur->val < val) traversal(cur->right, val);
return;

迭代方法

关于BST的迭代方法其实普遍都比较简单,因为二叉搜索树毕竟是有序的,遍历方向比较好控制。

迭代方法的基本逻辑就是:

  • 如果root为空,我们需要处理
if (root == nullptr){TreeNode* node = new TreeNode(val);return node;
}
  • 定义一个父结点parent,他是仅仅更在cur后面的结点,后续我们需要它来进行赋值操作
TreeNode* cur = root;
TreeNode* parent = root;
  • 通过迭代方法找到插入点的位置
while (cur != NULL){parent = cur;if (cur->val > val) cur = cur->left;if (cur->val > val) cur = cur->right;
}
  • 利用定义的parent进行赋值
//等我们跳出循环,parent就在插入位置的父结点位置
if (parent->val > val) parent->left = node;
else parent->right = node;
return root;

CPP代码

递归有返回值

class Solution {
public:TreeNode* insertIntoBST(TreeNode* root, int val) {if (root == NULL) {TreeNode* node = new TreeNode(val);return node;}if (root->val > val) root->left = insertIntoBST(root->left, val);if (root->val < val) root->right = insertIntoBST(root->right, val);return root;}
};

递归无返回值

class Solution {
private:TreeNode* parent;void traversal(TreeNode* cur, int val) {if (cur == NULL) {TreeNode* node = new TreeNode(val);if (val > parent->val) parent->right = node;else parent->left = node;return;}parent = cur;if (cur->val > val) traversal(cur->left, val);if (cur->val < val) traversal(cur->right, val);return;}public:TreeNode* insertIntoBST(TreeNode* root, int val) {parent = NULL;//把结点初始化一下子if (root == NULL) {root = new TreeNode(val);}traversal(root, val);return root;}
};

迭代

class Solution {
public:TreeNode* insertIntoBST(TreeNode* root, int val) {if (root == NULL) {TreeNode* node = new TreeNode(val);return node;}TreeNode* cur = root;TreeNode* parent = root; // 这个很重要,需要记录上一个节点,否则无法赋值新节点while (cur != NULL) {parent = cur;if (cur->val > val) cur = cur->left;else cur = cur->right;}TreeNode* node = new TreeNode(val);if (val < parent->val) parent->left = node;// 此时是用parent节点的进行赋值else parent->right = node;return root;}
};

450.删除二叉搜索树中的结点

力扣题目链接

文章讲解:450.删除二叉搜索树中的结点

视频讲解:调整二叉树的结构最难!| LeetCode:450.删除二叉搜索树中的节点

状态:由于删除二叉搜索树可能涉及到二叉搜索树结构的改变,所以一定要注意分情况讨论,详细的代码实现也一定要记住。

思路(分析五种情况)

  1. 首先要注意,二叉树是链式存储的,所以我们的删除不是真的删除,而是父结点指向新的孩子

  2. 本题相对于之前添加结点的操作,本题要难很多。因为删除结点我们必须要改变二叉树的结构

没找到删除的点

遍历到空结点直接返回

if (root == nullptr) return root;

删的点是叶子结点

左右孩子都为空,直接删除结点,返回NULL为根结点

if (root->left == nullptr && root->right == nullptr){//内存释放delete root;return nullptr;
}

要删的结点左为空,右为空

其左孩子为空,右孩子不为空,删除节点,右孩子补位 ,返回右孩子为根节点

else if (root->left == nullptr){auto retNode = root->left;delete root;return retNode;
}

要删的结点左不为空,右为空

其右孩子为空,左孩子不为空,删除节点,左孩子补位,返回左孩子为根节点

else if (root->right == nullptr){auto retNode = root->left;delete root;return reNode;
}

要删的结点左右都不为空

这里就讲究了,涉及到插入结点的操作,因为我们可以让待删结点的右孩子来代替删除位置,那么待删结点的左孩子就必须连根带叶得插入到右孩子,这里我们继续延续701.二叉搜索树中的插入操作中的思想,直接作为右孩子的叶子即可

else{TreeNode* cur = root->right; //找右子树最左面的结点while(cur->left != nullptr){cur = cur->left}cur->left = root->left; //把要删除的结点(root)左子树放在cur的左孩子位置TreeNode* tmp = root;  //把root结点保存一下,下面来删除root = root->right;		//返回旧root的右孩子作为新rootdelete tmp;	//释放结点内存return root;
}

伪代码实现

  • 确定递归函数的返回值和参数:返回值就是新结点的根结点;然后root是带删结点,key就是要删除的值
TreeNode* delete(root, key){}
//该函数就是leetCode提供的主函数
  • 确定递归的终止条件:

    • 在本题中,我们不是要遍历整颗二叉树才开始终止,其实只要找到了我们要删除的点就得删。那么既然我们找到了要删除的点了,所以删除逻辑也要写出来
    //没找到要删除的结点
    if (root == NULL) return NULL;
    if (root->val == key){if (root->left == NULL && root->right == NULL)return NULL;//这里的NULL return到哪,其实是返回到被删叶子结点的父结点了else if (root->left != NULL && root->right == NULL) return root->left;//让待删结点的左子树直接返回到待删结点的父结点那儿,完成待删结点的移除else if (root->left == NULL && root->right != NULL)return root->rightl;else{ //先找到待删结点最左侧的值代替位置cur = root->right;while(cur->left != NULL) cur = cur->left;//现在cur指向了右孩子的最左叶子cur->left = root->left; //完成待删结点的左子树连入右子树的叶子return root->right; //真正删除待删结点}
    }
    
  • 单层递归逻辑

//这里的root->left与上文的代码对应,就是接住被删结点的孩子。这里的逻辑非常重要!
if (key < root->val) root->left = delete(root->left, key);
if (key > root->val) root->right = delete(root->right, key);
return root;

CPP总体代码

class Solution {
public:TreeNode* deleteNode(TreeNode* root, int key) {if (root == nullptr) return root; // 第一种情况:没找到删除的节点,遍历到空节点直接返回了if (root->val == key) {// 第二种情况:左右孩子都为空(叶子节点),直接删除节点, 返回NULL为根节点if (root->left == nullptr && root->right == nullptr) {///! 内存释放delete root;return nullptr;}// 第三种情况:其左孩子为空,右孩子不为空,删除节点,右孩子补位 ,返回右孩子为根节点else if (root->left == nullptr) {auto retNode = root->right;///! 内存释放delete root;return retNode;}// 第四种情况:其右孩子为空,左孩子不为空,删除节点,左孩子补位,返回左孩子为根节点else if (root->right == nullptr) {auto retNode = root->left;///! 内存释放delete root;return retNode;}// 第五种情况:左右孩子节点都不为空,则将删除节点的左子树放到删除节点的右子树的最左面节点的左孩子的位置// 并返回删除节点右孩子为新的根节点。else {TreeNode* cur = root->right; // 找右子树最左面的节点while(cur->left != nullptr) {cur = cur->left;}cur->left = root->left; // 把要删除的节点(root)左子树放在cur的左孩子的位置TreeNode* tmp = root;   // 把root节点保存一下,下面来删除root = root->right;     // 返回旧root的右孩子作为新rootdelete tmp;             // 释放节点内存(这里不写也可以,但C++最好手动释放一下吧)return root;}}if (root->val > key) root->left = deleteNode(root->left, key);if (root->val < key) root->right = deleteNode(root->right, key);return root;}
};

二叉搜索树的迭代法删除结点

class Solution {
private:// 将目标节点(删除节点)的左子树放到 目标节点的右子树的最左面节点的左孩子位置上// 并返回目标节点右孩子为新的根节点// 是动画里模拟的过程TreeNode* deleteOneNode(TreeNode* target) {if (target == nullptr) return target;if (target->right == nullptr) return target->left;TreeNode* cur = target->right;while (cur->left) {cur = cur->left;}cur->left = target->left;return target->right;}
public:TreeNode* deleteNode(TreeNode* root, int key) {if (root == nullptr) return root;TreeNode* cur = root;TreeNode* pre = nullptr; // 记录cur的父节点,用来删除curwhile (cur) {if (cur->val == key) break;pre = cur;if (cur->val > key) cur = cur->left;else cur = cur->right;}if (pre == nullptr) { // 如果搜索树只有头结点return deleteOneNode(cur);}// pre 要知道是删左孩子还是右孩子if (pre->left && pre->left->val == key) {pre->left = deleteOneNode(cur);}if (pre->right && pre->right->val == key) {pre->right = deleteOneNode(cur);}return root;}
};

普通二叉树的删除方式

普通二叉树的删除方式就必须遍历整颗树,用交换值的操作来删除目标结点。

代码中目标结点(待删除的结点)被操作了两次:

  • 第一次是和目标结点的右子树最左面结点交换。
  • 第二次直接被NULL覆盖。
class Solution {
public:TreeNode* deleteNode(TreeNode* root, int key) {if (root == nullptr) return root;if (root->val == key) {if (root->right == nullptr) { // 这里第二次操作目标值:最终删除的作用return root->left;}TreeNode *cur = root->right;while (cur->left) {cur = cur->left;}swap(root->val, cur->val); // 这里第一次操作目标值:交换目标值其右子树最左面节点。}root->left = deleteNode(root->left, key);root->right = deleteNode(root->right, key);return root;}
};

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

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

相关文章

E1芯片如何实现同步和重同步

E1数据中包含有帧定位&#xff08;FAS&#xff09;信号&#xff0c;其次它还可以包含多帧格式&#xff0c;或者是CRC4或者是CAS随路信令格式&#xff0c;也可以是两者都有。 FAS 同步 芯片通常通过搜寻FAS头来建立同步或重同步。当它发现一个正确的FAS字&#xff08;X0011011&a…

基于springboot实现师生共评作业管理系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现师生共评作业管理系统演示 摘要 随着信息互联网信息的飞速发展&#xff0c;无纸化作业变成了一种趋势&#xff0c;针对这个问题开发一个专门适应师生作业交流形式的网站。本文介绍了师生共评的作业管理系统的开发全过程。通过分析企业对于师生共评的作业管理…

数据库:SQL分类之DQL详解

1.DQL语法 select 字段列表 from 表名列表 where 条件列表 group by 分组字段列表 having 分组后条件列表 order by 排序字段列表 limit 分页参数 基本查询 条件查询&#xff08;where&#xff09; 聚合函数&#xff08;count、max、min、avg、sum &#xff09; 分组查询&…

word文档显示异常,mac安装word字体:仿宋gb2312、方正小标宋简体

因为mac没有仿宋gb2312字体&#xff0c;windows上word里显示的仿宋gb2312字体与排版&#xff0c;在mac上显示为黑体、排版也错乱了&#xff0c;得不到想要打印格式。 需要安装仿宋gb2312字体&#xff08;同理 方正小标宋简体&#xff09; 下载&#xff1a;仿宋GB2312.zip&…

代码整洁?我后悔重构了代码

原文&#xff1a;Dan Abramov - 2020.01.11 那是一个深夜。 我的同事刚刚提交了他们一周编写的代码。我们正在开发一个图形编辑器的画布&#xff0c;他们实现了通过拖动边缘的小手柄&#xff0c;来调整形状&#xff08;如矩形和椭圆&#xff09;的大小的功能。 代码是有效的…

实习僧网站的实习岗位信息分析

目录 背景描述数据说明数据集来源问题描述分析目标以及导入模块1. 数据导入2. 数据基本信息和基本处理3. 数据处理3.1 新建data_clean数据框3.2 数值型数据处理3.2.1 “auth_capital”&#xff08;注册资本&#xff09;3.2.2 “day_per_week”&#xff08;每周工作天数&#xf…

TFT显示屏驱动

REVIEW 已经学习过VGA 时序与实现-CSDN博客 VGA 多分辨率-CSDN博客 今天就来让TFT屏显示一下 ACZ702开发板管脚信息表 - ACZ702开发板 - 芯路恒电子技术论坛 - Powered by Discuz! (corecourse.cn) 小梅哥视频&#xff1a;24 RGB TFT显示屏原理与驱动实现_哔哩哔哩_bilibili …

活动图高阶讲解-16

77 00:05:39,520 --> 00:05:41,520 另外一个就是循环 78 00:05:41,520 --> 00:05:45,520 如果怎么样 79 00:05:45,520 --> 00:05:47,520 就再做一遍 80 00:05:47,520 --> 00:05:49,520 如果还满足条件就再做一遍 81 00:05:49,520 --> 00:05:51,520 那就是循…

TG-12F使用SDK对接阿里生活物联网平台

文章目录 前言一、注意二、准备1. 安装Ubuntu&#xff08;版本20.04 X64&#xff09;程序运行时库。按顺序逐条执行命令&#xff1a;2. 安装Ubuntu&#xff08;版本20.04 X64&#xff09;依赖软件包。按照顺序逐条执行命令&#xff1a;3. 安装Python依赖包。按照顺序逐条执行命…

[spring] Spring Boot REST API - CRUD 操作

Spring Boot REST API - CRUD 操作 这里主要提一下 spring boot 创建 rest api&#xff0c;并对其进行 CRUD 操作 jackson & gson 目前浏览器和服务端主流的交互方式是使用 JSON(JavaScript Object Notation)&#xff0c;但是 JSON 没有办法直接和 Java 的 POJO 创建对应…

python-numpy(3)-线性代数

一、方程求解 参考资料 对于Ax b 这种方程&#xff1a; np.linalg.inv(A).dot(B)np.linalg.solve(A,b) 1.1 求解多元一次方程一个直观的例子 # AXB # X A^(-1)*B A np.array([[7, 3, 0, 1], [0, 1, 0, -1], [1, 0, 6, -3], [1, 1, -1, -1]]) B np.array([8, 6, -3, 1]…

cannot import name ‘get_host‘ from ‘urllib3.util.url‘

Error in py_module_import(module, convert convert) : ImportError: cannot import name get_host from urllib3.util.url (D:\\url.py) Run reticulate::py_last_error() for details. 这个错误表明在 urllib3 模块的 util.url 子模块中找不到名为 get_host 的函数。这可能…

第十五届蓝桥杯省赛C/C++大学B组真题及赛后总结

目录 个人总结 C/C 组真题 握手问题 小球反弹 好数 R 格式 宝石组合 数字接龙 爬山 拔河 ​编辑 再总结及后续规划 个人总结 第一次参加蓝桥杯&#xff0c;大二&#xff0c;以前都在在学技术&#xff0c;没有系统的学过算法。所以&#xff0c;还是花了挺多时间去备…

Rust - 所有权

所有的程序都必须和计算机内存打交道&#xff0c;如何从内存中申请空间来存放程序的运行内容&#xff0c;如何在不需要的时候释放这些空间&#xff0c;成了重中之重&#xff0c;也是所有编程语言设计的难点之一。在计算机语言不断演变过程中&#xff0c;出现了三种流派&#xf…

基于深度学习的花卉检测系统(含PyQt界面)

基于深度学习的花卉检测系统&#xff08;含PyQt界面&#xff09; 前言一、数据集1.1 数据集介绍1.2 数据预处理 二、模型搭建三、训练与测试3.1 模型训练3.2 模型测试 四、PyQt界面实现参考资料 前言 本项目是基于swin_transformer深度学习网络模型的花卉检测系统&#xff0c;…

软考125-上午题-【软件工程】-传统软件的测试策略

一、传统软件的测试策略 有效的软件测试实际上分为4步进行&#xff0c;即&#xff1a;单元测试、集成测试、确认测试、系统测试。 1-1、单元测试&#xff08;模块测试&#xff09; 单元测试也称为模块测试&#xff0c;在模块编写完成且无编译错误后就可以进行。 单元测试侧重…

温故知新之-TCP Keepalive机制及长短连接

[学习记录] 前言 TCP连接一旦建立&#xff0c;只要连接双方不主动 close &#xff0c;连接就会一直保持。但建立连接的双方并不是一直都存在数据交互&#xff0c;所以在实际使用中会存在两种情况&#xff1a;一种是每次使用完&#xff0c;主动close&#xff0c;即短连接&…

JVM虚拟机(五)强引用、软引用、弱引用、虚引用

目录 一、强引用二、软引用三、弱引用四、虚引用五、总结 引文&#xff1a; 在 Java 中一共存在 4 种引用&#xff1a;强、软、弱、虚。它们主要指的是&#xff0c;在进行垃圾回收的时候&#xff0c;对于不同的引用垃圾回收的情况是不一样的。下面我们就一起来看一下这 4 种引用…

51单片机实验03-单片机定时/计数器实验

目录 一、实验目的 二、实验说明 1、51单片机有两个16位内部计数器/定时器&#xff08;C/T&#xff0c; Counter/Timer&#xff09;。 2、模式寄存器TMOD 1) M1M0工作模式控制位&#xff1b; 2) C/T定时器或计数器选择位&#xff1a; 3&#xff09;GATE定时器/计数器运行…

软考系规第2章思维导图,软硬件网络和次新技术大杂烩

虽然目前系统规划与管理师的教程是否改版存在不确定性&#xff0c;但是不影响咱们先概要了解当前的教程&#xff0c;使用思维导图的方式粗读教程。 为了帮助你更好的学习系规教程&#xff0c;降低系规教程阅读门槛&#xff0c;指尖疯特发起了教程伴读活动&#xff0c;通过伴读脑…