您的位置:首页 > 理论基础 > 数据结构算法

leetcode【数据结构简介】《二叉搜索树》卡片——二叉搜索树中的基本操作

2020-04-21 21:29 579 查看

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;
    }

    删除操作

    删除要比我们前面提到过的两种操作复杂许多。

    有许多不同的删除节点的方法,这篇文章中,我们只讨论一种使整体操作变化最小的方法。

    我们的方案是用一个合适的子节点来替换要删除的目标节点。

    根据其子节点的个数,我们需考虑以下三种情况:

    1. 如果目标节点没有子节点,我们可以直接移除该目标节点。
    2. 如果目标节只有一个子节点,我们可以用其子节点作为替换。
    3. 如果目标节点有两个子节点,我们需要用其中序后继节点或者前驱节点来替换,再删除该目标节点。

    下面是三种情况的演示动态图:

    一帧帧画,一帧帧截图。如果对你理解有帮助,请务必给个👍

    删除二叉搜索树中的结点🚩

    给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。

    分析

    本来我使用迭代法,代码又长,情况还多,吐血三升。

    所以半路出家,决定使用递归法。

    下面为介绍。

    主要步骤为两步:搜索 & 删除

    关于搜索:搜索不到就返回原root,否则执行删除操作

    关于删除:分为两种情况

    1. 如果右子树为空,则返回左子树的root;
    2. 如果右子树非空,找到其顺序后继,对指来指去的指针处理好了之后,返回其顺序后继。

    动态演示

    说实话,直接阅读代码不如给你看动态图,了解具体操作步骤之后,用代码实现操作步骤是迟早的事。

    于是我们直接从右子树不为空的情况开始。

    先是比较简单的情况,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;
    }

    都看到这里了,确定不点个赞再走?(╯▔皿▔)╯

    • 点赞 2
    • 收藏
    • 分享
    • 文章举报
    Whywait_1 发布了44 篇原创文章 · 获赞 26 · 访问量 2036 私信 关注
    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: