博客
关于我
450 删除二叉搜索树中的节点(递归删除节点)
阅读量:353 次
发布时间:2019-03-04

本文共 6065 字,大约阅读时间需要 20 分钟。

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

 

转载地址:http://mhgr.baihongyu.com/

你可能感兴趣的文章
jQuery中的动画
查看>>
狂神说MySQL01:初识MySQL
查看>>
1.2.3 项目、项目集、项目组合以及运营管理之间的关系
查看>>
光环和你一起迎接改版
查看>>
【△重点△】LeetCode - 4. 寻找两个正序数组的中位数——二分查找
查看>>
LeetCode - 5. 最长回文子串——字符串、动态规划
查看>>
全局锁和表锁 :给表加个字段怎么有这么多阻碍?
查看>>
事务到底是隔离的还是不隔离的?
查看>>
SpringMVC的Model对象的使用
查看>>
@Import注解---导入资源
查看>>
解决ubuntu在虚拟机(VMware)环境下不能联网的问题
查看>>
二分查找与插入排序的结合使用
查看>>
892 三维形体的表面积(分析)
查看>>
40. 组合总和 II(dfs、set去重)
查看>>
16 最接近的三数之和(排序、双指针)
查看>>
279 完全平方数(bfs)
查看>>
410 分割数组的最大值(二分查找、动态规划)
查看>>
875 爱吃香蕉的珂珂(二分查找)
查看>>
450 删除二叉搜索树中的节点(递归删除节点)
查看>>
桌面图标的自动排列图标
查看>>