leetcode450.删除二叉搜索树中的节点(递归+迭代法)C++实现

删除二叉搜索树中的节点

题目要求:给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:1. 首先找到需要删除的节点;2. 如果找到了,删除它。
解决这道题的关键是要分情况讨论:

  1. 要删除的结点无孩子结点 (直接删),这是毫无疑问的,删除我后,父亲节点直接指向空。
  2. 要删除的结点只有左孩子结点(直接删),我的孩子只有一个,删除我后,右为空,将父亲指向我的左。
  3. 要删除的结点只有右孩子结点 (直接删),删除我后,左为空,将父亲指向我的右。 总的来说父亲带的孩子数不变,原来带了我,但是我删除了,最后又带了我的孩子。
  4. 要删除的结点左、右孩子结点均存在 (间接删),我有两个孩子,不能全都托付给父亲,孩子太多了!-> 使用替换删除法,在它的右子树中寻找中序下的第一个结点(节点值最小),或者是在它的左子树中寻找中序下的最后一个节点(节点值最大),将该节点的值和被删除节点的值进行交换,最终待删除的节点转移到了最底端,转化成了第1 、2、3种情况的删除问题。
    下面借助画图来说明:
  • 删除叶子结点(属于第1种情况):只需要知道要删除节点的父节点,将父节点置为NULL,删除子节点即可。
    在这里插入图片描述
  • 删除只有一个孩子节点(属于第2、3种情况):需要知道要删除节点的父节点,关键问题是要用父节点的左还是右来指向? 这个要好好思考,下图中可能是左吗?不可能,想想二叉搜索树的性质!
    在这里插入图片描述
    还有个要注意的是,下面这种情况就找不到父节点,此时的根节点就是要删除的节点,这个好办,将根节点转移,删除旧的根节点即可。
    在这里插入图片描述
  • 删除节点有两个孩子节点(第4种情况):
    方法一:找左子树的最大节点(7) --> 将最大节点(7)和要删除的节点进行交换 --> 删除的位置转移到了蓝色圆圈 --> 让其父节点连接它的左即可 (它的右一定是空,左不一定为空)
    在这里插入图片描述
    方法二:找右子树的最小节点(10) --> 将最小节点(10)和要删除的节点进行交换 --> 删除的位置转移到了蓝色圆圈 --> 让其父节点连接它的右即可 (它的左一定是空,右不一定为空)
    在这里插入图片描述

递归法

方法一:使用上一层栈帧的节点来接受递归返回的子树根节点。

TreeNode* deleteNode(TreeNode* root, int key) 
{
    if(root == nullptr) return nullptr;
    
    // 注意递归返回值一定是当前子树的根节点, 这个根节点由上一层接受
    // 递归往左走就由上一层的left接受, 递归往右走就由上一层的right接受
    if(root->val > key) // 当前值比目标值要大, 继续递归左子树寻找目标节点
        root->left = deleteNode(root->left, key);
    else if(root->val < key) // 当前值比目标值要小, 继续递归右子树寻找目标节点
        root->right = deleteNode(root->right, key);
    else // 当前值 = 目标值, 删除目标节点
    {
        // 包括叶子结点 + 左为空, 右不为空(两种情况合并)
        if(root->left == nullptr)
            return root->right;
        // 左不为空, 右为空
        else if(root->right == nullptr)
            return root->left;
        // 左右均不为空
        else
        {
            // 先找到右子树的最小节点
            TreeNode* MinRight = root->right;
            while(MinRight->left) MinRight = MinRight->left;
            // 将MinRight的值和root的值进行替换
            swap(root->val, MinRight->val);
            // 不要直接返回MinRight->right,会出大问题!因为此时要删除的节点跑到右子树最左边去了
            // , root离MinRight可能还隔着很多个节点, 要继续递归右子树, 找到下一次删除的位置
            root->right = deleteNode(root->right, key);
        }
    }
    return root;
}

方法二:形参传别名,别名就是上一层递归的左或者右,给别名的赋值就是间接的改变连接。

void traversal(TreeNode*& root, int key)
{
	if(root == nullptr) return;
	
	// 当前值比目标值要大, 继续递归左子树寻找目标节点
	if(root->val > key) traversal(root->left, key);
	// 当前值比目标值要小, 继续递归右子树寻找目标节点
	else if(root->val < key) traversal(root->right, key);
	// 当前值 = 目标值, 删除目标节点
	else 
	{
	    // 包括叶子结点 + 左为空, 右不为空(两种情况合并)
	    // 赋值运算符左边的root是上一层中root->right的别名, 也就是将当前层root的right链接到上一层中的root->right
	    if(root->left == nullptr)
	        root = root->right; 
	    // 左不为空, 右为空
	    else if(root->right == nullptr)
	        root = root->left;
	    // 左右均不为空
	    else
	    {
	        // 先找到右子树的最小节点
	        TreeNode* MinRight = root->right;
	        while(MinRight->left) MinRight = MinRight->left;
	        // 将MinRight的值和root的值进行替换
	        swap(root->val, MinRight->val);
	        traversal(root->right, key);
	    }
	    return;
	}
}
TreeNode* deleteNode(TreeNode* root, int key) {
	traversal(root, key);
	return root;
}

时间复杂度: O(N)。因为是二叉搜索树的搜索,比较好的情况是logN,最坏的情况是单链表,为O(N)。
空间复杂度: O(N)。最坏的情况要建立N层栈帧,递归深度为N。

迭代法

迭代法考虑的问题稍微多一点,总的思路都一样,先找,后删。

class Solution {
public:
    TreeNode* deleteNode(TreeNode* root, int key) {
        TreeNode* cur = root, *prev = nullptr;
        while(cur)
        {
            if(cur->val > key) 
            {
                prev = cur;
                cur = cur->left;
            }
            else if(cur->val < key) 
            {
                prev = cur;
                cur = cur->right;
            }
            else
            {
            	// 包括叶子结点 + 左为空, 右不为空(两种情况合并)
                if(cur->left == nullptr)
                {
                	// 考虑要删除的 cur为根节点的情况
                    if(cur == root)
                    {
                        root = cur->right; // 根节点转移
                    }
                    else
                    {
                        if(cur == prev->left) prev->left = cur->right;
                        else if(cur == prev->right) prev->right = cur->right;
                    }
                }
                // 左不为空, 右为空
                else if(cur->right == nullptr)
                {
                    if(cur == root)
                    {
                        root = cur->left;
                    }
                    else
                    {
                        if(cur == prev->left) prev->left = cur->left;
                        else if(cur == prev->right) prev->right = cur->left;
                    }  
                }
                // 左右均不为空
                else
                {
                    // 找右子树的最小节点
                    TreeNode* MinRTree = cur->right; // 右子树的最小节点
                    TreeNode* PMinRTree = cur; // 右子树的最小节点的父节点
                    while(MinRTree->left)
                    {
                        PMinRTree = MinRTree;
                        MinRTree = MinRTree->left;
                    }// 循环出来,MinRTree就是最小节点

                    // 将最小节点和cur的值进行替换, 并将PMinRTree指向MinRTree的右孩子节点
                    // 值得注意: MinRTree不一定是叶子结点啊!
                    cur->val = MinRTree->val;
                    
                    // 删除右子树的最左结点
                    if(MinRTree == PMinRTree->left) PMinRTree->left = MinRTree->right;
                    if(MinRTree == PMinRTree->right) PMinRTree->right = MinRTree->right;
                }
                return root;
            }
        }
        return root;
    }
};

思考:为什么前两种情况要考虑删除节点为根节点的情况,而第三种不需要考虑?
因为前两种情况根节点删除之后就不存在了,需要新的节点去担任根节点;而第三种情况,原根节点还存在,只不过删除的节点转移到了右子树的最左节点!
时间复杂度: O(N)。因为是二叉搜索树的搜索,比较好的情况是logN,最坏的情况是单链表,为O(N)。
空间复杂度: O(1)。因为迭代只建立了有限个变量,开辟空间为常数。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值