450 删除二叉搜索树中的节点(递归删除节点)

1. 问题描述:

给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:首先找到需要删除的节点;如果找到了,删除它。

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

示例:

root = [5,3,6,2,4,null,7]
key = 3

    5
   / \
  3   6
 / \   \
2   4   7

给定需要删除的节点值是 3,所以我们首先找到 3 这个节点,然后删除它。
一个正确的答案是 [5,4,6,2,null,null,7], 如下图所示。

    5
   / \
  4   6
 /     \
2       7

另一个正确答案是 [5,2,6,null,4,null,7]

    5
   / \
  2   6
   \   \
    4   7

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/delete-node-in-a-bst

2. 思路分析:

① 对于树的相关题目都是可以使用递归来解决的(因为树本身就是递归定义的),官方中提供的递归删除的思路比较好理解,下面是我自己理解之后的一些看法。

② 因为是二叉搜索树,所以可以利用左子树的值都比根节点要小,右子树的值都比根节点的要大的性质来递归寻找需要删除的节点,并且在递归的时候需要连接根节点与左右子树的关系,这样删除节点之后节点之间的关系才会变化,当我们递归到当前节点的值等于需要删除节点的值之后说明当前的根节点就是我们需要删除的节点,对删除的节点分情况讨论即可:

(1) 删除的节点是叶子节点,直接删除即可(将其置为null即可)

(2) 删除的节点左子树或者是右子树,将左子树或者右子树提到根节点即可

(3) 删除的节点存在左右子树,这个时候我们需要找到当前节点的前驱或者是后继,然后递归删除即可

对于第(2)种情况我们我们需要找到当前根节点的左子树的最大的节点,因为是在当前根节点的左子树中寻找的最大的那个节点所以我们下一次递归删除的节点应该是当前根节点的左子树中寻找,并且值就为刚才找到的左子树的最大值,这样就实现了递归删除的目的,对于第(3)种情况与第(2)中情况也是类似的,也是需要递归进行删除

对于下面的图,对于第(1)种情况,比如删除节点为16,为叶子节点我们可以从根节点出发一直找到16这个节点,然后因为是叶子节点所以可以直接将其置为null,这样在返回上一层的时候就可以使用指针的连接关系将其删除掉了,比如节点18一直递归到12,在删除叶子节点的时候将其返回null,然后通过返回到上一层所以root.left= null(18.left = null),这个时候就删除掉了

对于第(2)种的情况例如删除90这个节点,发现90有左孩子这个时候需要找到90的左子树中的最大的节点为82,将90这个节点的值替换为82,然后执行递归删除的操作:因为是从90的左子树中找到的删除的节点所以这个时候需要递归90的左子树执行删除的操作,也就是对应下面的代码,删除的值就为82这个值,然后最后会递归到82这个叶子节点将其置为null返回,对于第(3)种的情况与第二种的情况是类似的只是将这里的左孩子节点换成了右孩子节点

连接节点之间的指向关系:root.left = self.deleteNode(root.left, key)

③ 除了上面比较好理解的递归删除的方法之外,在力扣的评论区中发现了一个比较优秀的解法,不过比较难以理解,它是直接修改指针的指向并且将节点置为空值来删除节点的,具体可以画出图会更好理解一点

与②中的类似这里也是需要在递归的时候建立节点之间的联系:

当找到删除的节点的时候根据不同的情况修改节点之间的关系:

假如删除的叶子节点直接范围null,当删除的节点存在左孩子或者是右孩子的时候那么直接返回存在的左孩子或者右孩子这样返回到上一层的时候节点直接的关系就可以修改了,其中最关键的是代码是删除同时存在左孩子与右孩子的情况,这个情况有点复杂但是可以借助于图来帮助理解怎么样修改节点之间指针关系:

3. 代码如下:

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


class Solution:
    def preOrder(self, root: TreeNode):
        if not root: return
        print(root.val, end=" ")
        self.preOrder(root.left)
        self.preOrder(root.right)

    # 寻找当前根节点左子树中最大值
    def predecessor(self, root: TreeNode):
        root = root.left
        while root.right:
            root = root.right
        return root.val

    # 寻找当前根节点右子树中最小值
    def successor(self, root: TreeNode):
        root = root.right
        while root.left:
            root = root.left
        return root.val

    # 递归删除二叉搜索树中的节点
    def deleteNode(self, root: TreeNode, key: int) -> TreeNode:
        if not root: return root
        if root.val < key:
            root.right = self.deleteNode(root.right, key)
        elif root.val > key:
            root.left = self.deleteNode(root.left, key)
        # 要删除的节点是当前的节点
        else:
            # 删除的节点是叶子节点
            if not root.left and not root.right:
                root = None
            # 当前删除的节点有一个左孩子
            elif root.left:
                root.val = self.predecessor(root)
                root.left = self.deleteNode(root.left, root.val)
            else:
                root.val = self.successor(root)
                root.right = self.deleteNode(root.right, root.val)
        return root

优化代码:删除存在左右孩子节点的时候找到左子树中最大节点进行删除

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right


class Solution:
    def preOrder(self, root: TreeNode):
        if not root: return
        print(root.val, end=" ")
        self.preOrder(root.left)
        self.preOrder(root.right)

    def find(self, root: TreeNode):
        if not root.right: return root
        return self.find(root.right)

    # 删除节点: 与上面的find方法是一一对应的因为也是需要寻找到左子树中最大的那个节点
    def delete(self, root: TreeNode):
        if not root.right: return root.left
        root.right = self.delete(root.right)
        return root

    def deleteNode(self, root: TreeNode, key: int) -> TreeNode:
        # 模拟力扣的评论区写的代码
        if not root: return root
        if root.val > key:
            root.left = self.deleteNode(root.left, key)
            return root
        elif root.val < key:
            root.right = self.deleteNode(root.right, key)
            return root
        else:
            # 当前节点就为删除的节点那么需要分为三种情况:
            # 叶子节点/有左孩子节点或者有右孩子节点或者左孩子节点与右孩子节点都有的情况
            if not root.left and not root.right:
                return None
            elif root.left and not root.right:
                # 存在左孩子节点直接修改节点之间的关系
                return root.left
            elif root.right and not root.left:
                return root.right
            else:
                # 左右孩子节点都有
                node = self.find(root.left)
                # 下面是核心的修改指针关系的代码(因为是找的是左子树所以需要先修改left指针否则会形成一个环)
                node.left = self.delete(root.left)
                node.right = root.right
                return node

java代码:删除存在左右孩子节点的时候找到右子树中最小节点进行删除(与上面的优化代码是类似的只是进行删除的时候删除后替代的根节点不一样)

class Solution {
    public TreeNode deleteNode(TreeNode root, int key) {
        if (root == null) {
            return null;
        }
        if (key < root.val) {
            // 待删除节点在左子树中
            root.left = deleteNode(root.left, key);
            return root;
        } else if (key > root.val) {
            // 待删除节点在右子树中
            root.right = deleteNode(root.right, key);
            return root;
        } else {
            // key == root.val,root 为待删除节点
            if (root.left == null) {
                // 返回右子树作为新的根
                return root.right;
            } else if (root.right == null) {
                // 返回左子树作为新的根
                return root.left;
            } else {
                // 左右子树都存在,返回后继节点(右子树最左叶子)作为新的根
                TreeNode successor = min(root.right);
                successor.right = deleteMin(root.right);
                successor.left = root.left;
                return successor;
            }
        }
    }

    private TreeNode min(TreeNode node) {
        if (node.left == null) {
            return node;
        }
        return min(node.left);
    }

    private TreeNode deleteMin(TreeNode node) {
        if (node.left == null) {
            return node.right;
        }
        node.left = deleteMin(node.left);
        return node;
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值