删除二叉搜索树中的节点【LeetCode 450】(两种方法的实现与比较)

目录

题目描述:

方法一:

方法二:

两种删除方法的比较:


题目描述:

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

要求算法时间复杂度为 O(h),h 为树的高度。

分析:

首先我们要明确:实现的是一个函数,最终需要返回删除之后的树的根节点。

由于这是一棵二叉搜索树,所以对于要删除的节点我们需要找到他并进行删除操作:
  如果当前节点值比key大,则需要删除当前节点的左子树中key对应的值;root.left = deleteNode(root.left,key);
  如果当前节点值比key小,则需要删除当前节点的右子树中key对应的值;  root.right = deleteNode(root.right,key);
  当前节点等于key,则需要删除当前节点,并保证二叉搜索树的性质不变

在删除当前节点时,我们会遇到3种情况,在下面的叙述中,我们均以删除节点5为例子。

情况一:当前节点没有左子树,直接返回当前节点的右孩子即可。(上层指针跳过了当前节点5,指向了右孩子7,就相当于删除了5)

情况二:当前节点没有右子树,直接返回当前节点的左孩子即可。(上层指针跳过了当前节点5,指向了左孩子3,就相当于删除了5)

        

情况三:当前节点既有左子树,又有右子树。

(这里可能有人会问,如果既没有左子树,又没有右子树怎么办? 这里把这种情况合并到情况一中,返回右子树,也就是返回NULL)

第三种情况比较复杂,这里介绍两种方法:

方法一:

将root的左子树放到root的右子树的最小节点的左子树上。
        这句话听起来计较绕,看图会很清晰,删除节点5,把5的左子树【2,3,4】看做一个整体,全部移动到右子树的最小节点6的左子树上。
        为什么是右子树的最小节点的左子树? 这是根据二叉搜索树的性质定的,也可以选择把root的右子树移动到左子树的最大节点大的右孩子上,目的都是保证二叉搜索树的性质不变。

        

代码:

class Solution {
public:
    //寻找以t为根的最小节点
    TreeNode* find_Min(TreeNode* t){
        if (t==NULL) return NULL;
        while (t->left!=NULL) t=t->left;
	    return t;
    }
    TreeNode* deleteNode(TreeNode* root, int key) {
        if (root==NULL) return NULL; //查找的递归出口,找不到,不做操作
        if (root->val==key){ //找到了要删除 
            if (root->left==NULL) return root->right; //情况一
            else if (root->right==NULL) return root->left;  //情况二
            else {  //情况三:方法一
                TreeNode* t=find_Min(root->right); //右子树的最小节点 
                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; //返回根 
    }
};

 

方法二:

用右子树的最小节点的值替换root的值,再递归的删除替换节点。

        如图,要删除节点5,先找到右子树的最小节点6,然后用6替换5,现在问题就转化成了在root的右子树里删除替换的节点6。这样一直递归下去,情况三最终一定会变成情况一二那种。然后完成删除操作。

代码:

class Solution {
public:
    //寻找以t为根的最小节点
    TreeNode* find_Min(TreeNode* t){
        if (t==NULL) return NULL;
        while (t->left!=NULL) t=t->left;
	    return t;
    }
    TreeNode* deleteNode(TreeNode* root, int key) {
        if (root==NULL) return NULL; //查找的递归出口,找不到,不做操作
        if (root->val==key){ //找到了要删除 
            if (root->left==NULL) return root->right; //情况一
            else if (root->right==NULL) return root->left;  //情况二
            else {  //情况三:方法二 
                TreeNode* t=find_Min(root->right);//右子树的最小节点 
                root->val=t->val;   //用最小值代替删除的节点(相当于删除了) 
                root->right=deleteNode(root->right,root->val); //递归的去删除右子树的替代节点 
            }
        }
        if (root->val > key)  root->left=deleteNode(root->left, key); //往左找
        if (root->val < key) root->right=deleteNode(root->right, key); //往右找
        return root; //返回根 
    }
};

两种删除方法的比较:

很明显,第一种方法:直接将左子树移植到右子树的最小节点上,更容易理解,而且删除操作是一步到位的,
第二种方法呢,递归的去删除,每次都要去替换删除结点,可能会执行很多次的替换。

虽然第二种方法比较难一点儿,但是教科书上一般都讲的是第二种方法,因为第二种方法删除后,二叉搜索树的性质更优。

同样一棵树,都是删除节点5,但是第二种方法的删除之后的树高度更小,更优。

      

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值