Authur Whywait 做一块努力吸收知识的海绵
想看博主的所有leetcode卡片学习笔记?传送门点这儿
- 了解如何在二叉搜索树中进行搜索、插入或删除;
- 在二叉搜索树中运用递归或迭代的方法,进行搜索、插入和删除操作;
- 了解节点数量、树的高度和操作复杂度之间的关系。
搜索操作
在本节中,我们将讨论如何在二叉搜索树中搜索特定的值。
根据BST的特性,对于每个节点:
- 如果目标值等于节点的值,则返回节点;
- 如果目标值小于节点的值,则继续在左子树中搜索;
- 如果目标值大于节点的值,则继续在右子树中搜索。
二叉搜索树中的搜索🚩
给定二叉搜索树(BST)的根节点和一个值。 你需要在BST中找到节点值等于给定值的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 NULL。
返回如下子树:
分析
由上文中关于BST特性的分析,此题思路显而易见
故而在下面直接贴上代码。
代码实现以及执行结果
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
struct TreeNode* searchBST(struct TreeNode* root, int val){
if(!root) return NULL;
if(root->val==val) return root;
else if(root->val>val) return searchBST(root->left, val);
else return searchBST(root->right, val);
}
插入操作
二叉搜索树中的另一个常见操作是插入一个新节点。
有许多不同的方法插入新节点,这里我们只讨论一种使整体操作变化最小的经典方法。
其主要思想是为目标节点找出合适的叶节点位置,然后将该节点作为叶节点插入。 因此,搜索将成为插入的起始。
与搜索操作类似,对于每个节点,我们将:
- 根据节点值与目标节点值的关系,搜索左子树或右子树;
- 重复步骤 1 直到到达外部节点;
- 根据节点的值与目标节点的值的关系,将新节点添加为其左侧或右侧的子节点。
这样,我们就可以添加一个新的节点并依旧维持二叉搜索树的性质。
与搜索操作相同,我们可以递归或迭代地进行插入。
二叉搜索树中的插入操作🚩
给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 保证原始二叉搜索树中不存在新值。
注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回任意有效的结果。
分析
如上文所述方法操作即可。
历程
看到题,so easy!
刷刷刷码完代码,然后发现输出的仍为原来的树。代码如下:
struct TreeNode* insertIntoBST(struct TreeNode* root, int val){
if(!root) return NULL;
struct TreeNode* temp = root;
while(temp) temp = temp->val<val ? temp->right : temp->left;
temp = (struct TreeNode *)malloc(sizeof(struct TreeNode));
temp->val = val;
temp->left = NULL;
temp->right = NULL;
return root;
}
王德发?然后各种找啊找,感觉这么“完美”的代码怎么会有错误呢?
这是一条tips:
我们在定义指针的时候,一定要对其初始化!一定要初始化!养成好习惯!
然后去喝了个茶(看了一下别人的代码),发现大家频繁有提到“父结点”这个词。
父结点?和父节点有何关系?
当然有关系啦~
我们跳出while循环的条件是temp为空,然后给temp分配空间,一切看似那么顺理成章。但是我却忽略了一个问题,就是如果temp为NULL,你给他分配了空间,不就是相当于type * a = ()malloc(); temp = a;
了吗?
用人话说的话,就是temp此时已经和树这个结构没有任何联系了。
更形象的说,malloc函数带着temp急需的物资(一个指定大小的空间)想接到temp上,认为可以投靠大树抱个大腿,然而temp只是从树顶滑动至树叶最后从叶尖滑下的一滴水珠罢了,他已经和树叶没有一丝联系,至于malloc函数抱大腿的想法,只能是空想罢了。
所以,malloc函数要确定自己和大树之间有没有建成联系,还是要从temp这个水滴接触过的最后一片叶子入手。确定了最后一片叶子,才可以保证自己的投诚(返回相应空间到一个地址)能达到自己预想的效果。
这最后一片叶子,就是他们所谓的父结点。
于是,对程序略作修改,即可。
代码实现以及执行结果
struct TreeNode* insertIntoBST(struct TreeNode* root, int val){
if(!root) return NULL;
struct TreeNode* temp = root, * father = NULL;
while(temp){
father = temp;
temp = temp->val<val ? temp->right : temp->left;
}
temp = (struct TreeNode *)malloc(sizeof(struct TreeNode));
temp->val = val;
temp->left = NULL;
temp->right = NULL;
if(father->val>val) father->left = temp;
else father->right = temp;
return root;
}
删除操作
删除要比我们前面提到过的两种操作复杂许多。
有许多不同的删除节点的方法,这篇文章中,我们只讨论一种使整体操作变化最小的方法。
我们的方案是用一个合适的子节点来替换要删除的目标节点。
根据其子节点的个数,我们需考虑以下三种情况:
- 如果目标节点没有子节点,我们可以直接移除该目标节点。
- 如果目标节只有一个子节点,我们可以用其子节点作为替换。
- 如果目标节点有两个子节点,我们需要用其中序后继节点或者前驱节点来替换,再删除该目标节点。
下面是三种情况的演示动态图:
一帧帧画,一帧帧截图。如果对你理解有帮助,请务必给个👍
删除二叉搜索树中的结点🚩
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
分析
本来我使用迭代法,代码又长,情况还多,吐血三升。
所以半路出家,决定使用递归法。
下面为介绍。
主要步骤为两步:搜索 & 删除
关于搜索:搜索不到就返回原root,否则执行删除操作
关于删除:分为两种情况
- 如果右子树为空,则返回左子树的root;
- 如果右子树非空,找到其顺序后继,对指来指去的指针处理好了之后,返回其顺序后继。
动态演示
说实话,直接阅读代码不如给你看动态图,了解具体操作步骤之后,用代码实现操作步骤是迟早的事。
于是我们直接从右子树不为空的情况开始。
先是比较简单的情况,root的子树直接为其顺序后继。
再是稍微复杂一些的,右子树的根结点不是顺序后继的情况。
一帧帧画图,如果顺序错了,或者哪里写错了没注意到就要重新回到原处重新画,画完再一帧帧截图。如此反复四五次之后,我决定不再修改了。虽然里面有非常小的偏差(和后面附上的代码),但是并不影响读者了解具体操作步骤。点个👍支持下呗o( ̄▽ ̄)o
代码实现以及执行结果
struct TreeNode* deleteNode(struct TreeNode* root, int key){
if(!root) return root;
if(key > root->val) root->right = deleteNode(root->right, key);
else if(key < root->val) root->left = deleteNode(root->left, key);
else{
if(!root->right) return root->left;
else{
struct TreeNode* cur = root->right, * pre = NULL;
while(cur->left){
pre = cur;
cur = cur->left;
}
if(pre) pre->left = NULL;
cur->left = root->left;
if(pre){
struct TreeNode* rightCur = cur;
while(rightCur->right) rightCur = rightCur->right;
rightCur->right = root->right;
}
return cur;
}
}
return root;
}
都看到这里了,确定不点个赞再走?(╯▔皿▔)╯