代码随想录 | Day26 | 二叉树:二叉搜索树中的插入操作删除二叉搜索树中的节点修剪二叉搜索树

news2024/10/5 0:47:24

代码随想录 | Day26 | 二叉树:二叉搜索树中的插入操作&&删除二叉搜索树中的节点&&修剪二叉搜索树

主要学习内容:
二叉搜索树的插入删除操作

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

701. 二叉搜索树中的插入操作 - 力扣(LeetCode)

解法思路:

本质就是二叉搜索树的查找,找到插入的地方就行

val比本层结点t的值大,去右子树,小,去左子树,如果左子树或者右子树为空,直接把val赋值给它就完事。

1.函数参数和返回值

void tra(TreeNode *t,int val)

t当前节点,val是插入值

2.终止条件

本题只要赋值操作结束就是终止条件

3.本层代码逻辑

		//比本层小,去左子树
		if(t->val>val)
            if(t->left)
                tra(t->left,val);
			//左子树为空,直接赋值返回
            else
            {
                t->left=new TreeNode(val);
                return;
            }
		//比本层大,去右子树
        else
            if(t->right)
                tra(t->right,val);
			//右子树为空,直接赋值返回
            else
            {
                t->right=new TreeNode(val);
                return;
            }

完整代码:

class Solution {
public:
    void tra(TreeNode *t,int val)
    {
        if(t->val>val)
            if(t->left)
                tra(t->left,val);
            else
            {
                t->left=new TreeNode(val);
                return;
            }
        else
            if(t->right)
                tra(t->right,val);
            else
            {
                t->right=new TreeNode(val);
                return;
            }
    }
    TreeNode* insertIntoBST(TreeNode* root, int val) {
        if(root==nullptr)
        {
            root=new TreeNode(val);
            return root;
        }
        tra(root,val);
        return root;
    }
};

注意一下检查root是否为空就行

代码随想录做法
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;
    }
}

搜索过程:

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

赋值过程:

        if (root == NULL) {
            TreeNode* node = new TreeNode(val);
            return node;
        }

比自己写的要简洁很多,特殊情况也涵盖了进去

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

450. 删除二叉搜索树中的节点 - 力扣(LeetCode)

思路:

先分情况讨论,没找到就不说啦,找到的话可以大致分为3种情况

1.删除的是叶子结点,直接删

2.删的结点只有一个孩子,左或者右,那就让孩子直接继承到结点所在的位置即可

3.删的结点左右都有,这个比较麻烦,我选择的策略是把当前结点的左孩子接到右孩子上面。而这样的话,我们就要找到当前结点右孩子的最左边的左孩子,让当前结点的左孩子接上去,因为这里是只比当前结点大一点点的数,比当前结点的其他数都要小,只有在这里才符合二叉搜索树定义。

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

例如删除7的过程,就是如上。如果没有8的话,那5也是直接去8的位置。

接下来看具体的代码实现

1.函数返回值和参数

TreeNode* deleteNode(TreeNode* root, int key) 

我们直接就返回的是删除完以后的当前结点,可能是当前结点被删,也有可能是左子树或者右子树中的结点被删掉,然后一层一层往上返回。

2.本层逻辑

if(root->val>key)  root->left=deleteNode(root->left,key);
if(root->val<key) root->right=deleteNode(root->right,key);
return root;

左子树不为空,遍历左子树,用当前结点的左子树承接修改完以后的左子树根结点

右子树不为空,遍历右子树,用当前结点的右子树承接修改完以后的右子树根结点

左右子树处理完以后返回当前结点

如果删除的是本层结点的话,会在终止条件中处理

3.终止条件(其实我觉得也算是本层处理逻辑了)

步骤:

1.为空那就返回null

2.如果没找到那就跳到本层逻辑去继续递归左右子树

3.找到了的话就是进入下面这个if

​ 3.1 叶子结点 直接删

​ 3.2 左为空或者右为空 直接让孩子继承当前结点的位置,左不为空就返回左孩子,这里是通过本层逻辑里面的root->left承接住了这个返回值,右孩子同理

​ 3.3 左右都不为空

TreeNode *t=root->right;
while(t->left) t=t->left;
t->left=root->left;
return root->right;  

1.保存一下右孩子,因为我们的策略是让左孩子接到右孩子上面。

2.找到右子树中最左边的位置,即一直循环,循环到空为止

3.将当前结点左孩子移动到右子树最左边

4.返回新的子树的根结点,即当前结点的右子树

完整的本层逻辑代码:

if(root==nullptr)
	return nullptr;
if(root->val==key)
{
	if(root->left==nullptr&&root->right==nullptr)
		return nullptr;
	else if(root->left!=nullptr&&root->right==nullptr)
		return root->left;
	else if(root->left==nullptr&&root->right!=nullptr)
		return root->right;
	else
	{
		TreeNode *t=root->right;
		while(t->left) t=t->left;
		t->left=root->left;
		return root->right;  
	} 
}

完整代码:

class Solution {
public:
    TreeNode* deleteNode(TreeNode* root, int key) {
        if(root==nullptr)
            return nullptr;
        if(root->val==key)
        {
            if(root->left==nullptr&&root->right==nullptr)
                return nullptr;
            else if(root->left!=nullptr&&root->right==nullptr)
                return root->left;
            else if(root->left==nullptr&&root->right!=nullptr)
                return root->right;
            else
            {
                TreeNode *t=root->right;
                while(t->left) t=t->left;
                t->left=root->left;
                return root->right;  
            } 
        }
        if(root->val>key)  root->left=deleteNode(root->left,key);
        if(root->val<key) root->right=deleteNode(root->right,key);
        return root;
    }
};

注意我们这里是逻辑上的修改,物理上还需要手动释放内存,这里不过多赘述,大家自行注意。

代码随想录有释放内存的完整版:

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的右孩子作为新root
                delete 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;
    }
};

669.修剪二叉搜索树

669. 修剪二叉搜索树 - 力扣(LeetCode)

思路1:上一套题的后序遍历

上一道题是判断等不等于,这道题是判断是否在一个区间没错就改一个if条件就好。

哈哈其实没有那么简单,上一套道题本质上是一个前序遍历,我们找到一个符合的值,删掉了就直接return了,没管它的子树上的值有没有在区间内,所以我们这次要换成后序遍历,这样就是从最底下开始遍历,每一个节点都不会错过。

class Solution {
public:
    TreeNode* trimBST(TreeNode* root, int low, int high) {
        if(root==nullptr)
            return nullptr;
        //左
        if(root->left)  root->left=trimBST(root->left,low,high);
        //右
        if(root->right) root->right=trimBST(root->right,low,high);
        //中
        if(root->val>high || root->val<low)
        {
            if(root->left==nullptr&&root->right==nullptr)
                return nullptr;
            else if(root->left!=nullptr&&root->right==nullptr)
                return root->left;
            else if(root->left==nullptr&&root->right!=nullptr)
                return root->right;
            else
            {
                TreeNode *t=root->right;
                while(t->left) t=t->left;
                t->left=root->left;
                return root->right;  
            } 
        }
        return root;
    }
};

思路2:后序遍历

class Solution {
public:
    TreeNode* trimBST(TreeNode* root, int low, int high) {
        if(root==nullptr)
            return nullptr;
        root->left=trimBST(root->left,low,high);
        root->right=trimBST(root->right,low,high);
        if(root->val<low)
            return root->right;
        if(root->val>high)
            return root->left;
        return root;
    }
};

后序遍历,本层逻辑是如果遇到小于low的直接返回右孩子,大于high的直接返回左孩子。

有人可能疑惑这样的话不就不知道右孩子和左孩子是否全都符合区间了嘛?

其实后序遍历就避免了这个问题,我们看一个例子

image-20241004214149676

我们是后序遍历,所以先遍历到1发现1是,再到2,再到0发现0小于low,就返回右孩子,遍历4,4大于high,返回左孩子。

你会发现我们用后序遍历,左子树或者右子树的结点全是已经验证过的,留下的都是在区间内的。

思路3:(复习的时候再看一遍)

对根结点 root 进行深度优先遍历。对于当前访问的结点,如果结点为空结点,直接返回空结点;如果结点的值小于 low,那么说明该结点及它的左子树都不符合要求,我们返回对它的右结点进行修剪后的结果;如果结点的值大于 high,那么说明该结点及它的右子树都不符合要求,我们返回对它的左子树进行修剪后的结果;如果结点的值位于区间 [low,high],我们将结点的左结点设为对它的左子树修剪后的结果,右结点设为对它的右子树进行修剪后的结果。

class Solution {
public:
    TreeNode* trimBST(TreeNode* root, int low, int high) {
        if (root == nullptr) {
            return nullptr;
        }
        if (root->val < low) {
            return trimBST(root->right, low, high);
        } else if (root->val > high) {
            return trimBST(root->left, low, high);
        } else {
            root->left = trimBST(root->left, low, high);
            root->right = trimBST(root->right, low, high);
            return root;
        }
    }
};

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

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

相关文章

企业级数据备份一般都是怎么做的?来唠唠嗑

小白最近去了很多企业看了一下他们的存储方案&#xff0c;基本上都是单硬盘数据存储&#xff0c;一个硬盘10TB&#xff08;实际可用8TB左右&#xff09;。 这些大概是大部分微小企业存储数据的办法&#xff0c;也是他们能想到的最好办法了吧。 截至2024年的今天&#xff0c;咱…

MATLAB|基于多主体主从博弈的区域综合能源系统低碳经济优化调度

目录 主要内容 程序亮点&#xff1a; 模型研究 一、综合能源模型 二、主从博弈框架 部分代码 结果一览 下载链接 主要内容 程序参考文献《基于多主体主从博弈的区域综合能源系统低碳经济优化调度》&#xff0c;采用了区域综合能源系统多主体博弈协同优化方…

vim 操作

vim编辑器的有三种工作模式&#xff1a;命令模式、插入模式和底行命令模式 打开进入命令模式&#xff1a; 由命令模式到输入模式&#xff1a;i:在光标前插&#xff1b;a:在光标后插&#xff1b;o:在下一行插 由输入模式进入命令模式&#xff1a;esc 由命令模式进入底行命令…

判断推理(2)

集合推理: 这是不能串在一起的&#xff0c;再进行合并推理的时候有的一定要放在开头 D D 第二句属于真假推理 然后进行翻译推理的时候一定要让有的打头&#xff0c;所以让1或者是2打头&#xff0c;但是1如果是最开头的话那么就什么也推不出来&#xff0c;所以只能是2打头B B A…

Pikachu-Cross-Site Scripting-xss之htmlspecialchars

首先输入各种字符 查看页面元素&#xff0c;可以看到这里对一些符号做了转换&#xff0c;但是 单引号等几个符号没处理&#xff1b; 从代码上看&#xff1b;使用单引号做闭合&#xff1b; 构造payload a onclickalert(11) 提交&#xff0c;得到xss攻击

【Java】springboot 项目中出现中文乱码

在刚创建的springboot项目中&#xff0c;出现乱码&#xff0c;跟走着解决一下 1、Ctrl Shift S 打开idea设置&#xff0c;根据图片来&#xff0c;将③④这三个地方都修改为UTF-8 2、返回配置查看&#xff0c;解决

僵尸进程、孤儿进程和守护进程

让我们详细讨论僵尸进程、孤儿进程和守护进程。 1. 僵尸进程 (Zombie Process) 定义: 僵尸进程是指一个已经终止执行&#xff08;结束运行&#xff09;&#xff0c;但其父进程尚未对其进行清理&#xff08;调用wait()或waitpid()系统调用来获取子进程的退出状态&#xff09;的…

Docker 从安装到实战

Docker 是一个开源的平台&#xff0c;用于自动化应用程序的部署、扩展和管理。它利用操作系统级别的虚拟化&#xff0c;将应用程序及其依赖项封装在称为容器的轻量级、可移植的单元中。以下是 Docker 的一些关键特点&#xff1a; 容器化&#xff1a;Docker 容器可以在任何支持 …

(8)MATLAB瑞利衰落信道仿真1

文章目录 一、瑞利随机变量及其概率密度函数二、仿真代码三、仿真结果四、仿真代码的几点补充说明其他 一、瑞利随机变量及其概率密度函数 在无线通信中&#xff0c;如果信道中存在大量非视距路径而不存在视距路径信号分量&#xff0c;此时&#xff0c;无线信道可以由一个复高…

深入浅出,从源码搞清Bean的加载过程

深入浅出&#xff0c;从源码搞清Bean的加载过程 前言 Bean的加载过程算是面试中的老生常谈了&#xff0c;今天我们就来从源码层面深入去了解一下Spring中是如何进行Bean的加载的 Spring 先看示例代码&#xff1a; public static void main(String[] args) {ApplicationCon…

微服务之间的相互调用的几种常见实现方式对比

目录 微服务之间的相互调用的几种实现方式 一、HTTP HTTP/RESTful API调用工作原理 二、RPC 设计理念与实现方式 协议与传输层 RPC远程调用工作原理 应用场景与性能考量 特点 三、Feign 设计理念与实现方式 协议与传输层 Feign调用的基本流程 Feign调用的工作原理…

算法训练营打卡Day19

目录 1.二叉搜索树的最近公共祖先 2.二叉树中的插入操作 3.删除二叉搜索树中的节点 题目1、二叉搜索树的最近公共祖先 力扣题目链接(opens new window) 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有…

温度转换-C语言

1.问题&#xff1a; 输入一个华氏温度&#xff0c;要求输出摄氏温度。公式为 c5(F-32)/9&#xff0c;取位2小数。 2.解答&#xff1a; scanf("%lf",&f);或者scanf("%f",&f);如果你前面定义的f是用double类型的话&#xff0c;就应该用%lf格式&…

deploy thingsboard

ThingsBoard部署 平台&#xff1a;windows10&#xff0c;idea2022&#xff0c;postgres15 maven仓库 进入thingsboard源码下载目录: 主要执行以下两个命令&#xff1a; mvn编译&#xff1a; mvn clean install -Dmaven.test.skiptrue编译报错时&#xff1a; 清除java进程 t…

计算机毕业设计 玩具租赁系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

UART通信—基于江科大源码基础进行的改进和解析

我就不讲理论了&#xff0c;CSDN上大佬属实多&#xff0c;我就只讲代码了&#xff0c;串口的基本理论&#xff0c;大家去看其他大佬写的吧 一、源文件的组成 1、包含的头文件 stm32f10x.h 是STM32F10x系列微控制器的标准外设库&#xff08;Standard Peripheral Library&…

C语言基础(7)之操作符(1)(详解)

目录 1. 各种操作符介绍 1.1 操作符汇总表 2. 移位操作符 2.1 移位操作符知识拓展 —— 原码、反码、补码 2.2 移位操作符讲解 2.2.1 右移操作符 ( >> ) 2.2.2 左移操作符 ( << ) 3. 位操作符 3.1 & (按位与) 3.2 | (按位或) 3.3 ^ (按位异或) 3.4…

【AI学习】Mamba学习(二):线性注意力

上一篇《Mamba学习&#xff08;一&#xff09;&#xff1a;总体架构》提到&#xff0c;Transformer 模型的主要缺点是&#xff1a;自注意力机制的计算量会随着上下文长度的增加呈平方级增长。所以&#xff0c;许多次二次时间架构&#xff08;指一个函数或算法的增长速度小于二次…

C++ 多态:重塑编程效率与灵活性

目录 多态的概念 多态的定义及实现 多态的构成条件 虚函数 虚函数的重写 虚函数重写的两个例外&#xff1a; 1. 协变(基类与派生类虚函数返回值类型不同) 2. 析构函数的重写(基类与派生类析构函数的名字不同&#xff09; 析构函数要不要定义成虚函数&#xff1f;&…