算法_二叉树_删除二叉搜索树中的节点

删除二叉搜索树中的节点

leetcode链接

1.解法

迭代法

思路:如果想要删一个节点,我们要考虑很多种情况:

  1. 如果二叉搜索树中没有key对应的值:直接返回key即可

  2. 如果二叉搜索树中有key对应的值,并假设对应的节点为cur,cur的父节点为parent:

    因为我们知道二叉搜索树的性质:左子树的所有节点都小于该节点,右子树的所有节点都大于该节点,所以可根据这个性质推出下面几种操作:

    (1)cur为叶子节点,即既没有左孩子也没有右孩子,那么直接删除即可

    (2)cur有左孩子,但没有右孩子:直接把cur的左孩子赋给父节点parent的左孩子或者右孩子即可(因为cur的左孩子一定比cur小,而至于赋给parent的左还是右,要根据cur在parent的左边还是右边,和cur保持一致即可,如果cur.val>parent.val,则赋给parent.right;反之则赋给parent.left)
    在这里插入图片描述
    例如删除上图中的4,即cur=4,parent=5,我们只需要把4(cur)的左孩子赋给parent.left即可(cur也在parent.left)

    (3)cur有右孩子,但没有左孩子:直接把cur的右孩子赋给父节点parent的左孩子或者右孩子即可。

    还是上图,删除上图中的6,即cur=6,parent=5,我们只需要把6(cur)的右孩子赋给parent.right即可(cur也在parent.right)

    (4)cur既有左孩子,又有右孩子:因为cur的左子树的所有值都小于cur,我们用a表示左子树的所有值,即max(a)<cur.val,而cur的右子树的所有值又都大于cur,我们用b表示右子树的所有值,即min(b)>cur.val,所以我们有max(a)<cur.val<min(b),而min(b)一定出现在cur右子树的最左边的一个值,该节点记为k,所以我们只要把cur的左子树接到k的左子树,然后把cur的右子树接到parent的左孩子或者右孩子即可
    在这里插入图片描述
    例如:要删除2,那么我们就要找2的右子树的最左边的那个值,发现是3,所以我们把2的左子树1接到3的左子树,然后把2的右子树接到parent.left(因为cur也在parent.left)

    分析完这四种情况,我们发现第三种(cur有右孩子,但没有左孩子)和第四种(cur既有左孩子,又有右孩子)都有把cur的右孩子赋给父节点parent的左孩子或者右孩子即可这步操作,所以我们可以把他们融为一种情况,只不过第三种情况是第四种情况的一个特例,即cur的左子树为None,我们一样可以用第四种情况的方法来操作第三种情况。

    而前两种我们也可以把第一种情况(cur为叶子节点,即既没有左孩子也没有右孩子)作为第二种情况(cur有左孩子,但没有右孩子)的特例,只不过第一种情况cur的左孩子为None。也可以用第二种情况的方法来操作第一种情况。

    所以总结起来一共是两种情况(cur有左孩子,但没有右孩子)(cur既有左孩子,又有右孩子)。我们可以用cur是否有右子树来作为判断依据。

    说了这么多我们都是建立在cur有parent的情况下进行的,但parent一定存在吗?不一定,因为cur有可能就是根节点,他们有parent,所以如果cur就是根节点的话,我们就不用赋值给parent的孩子了,如果是第一种情况(cur有左孩子,但没有右孩子),那么直接返回cur.left即可,这和我们上面的操作一致,只是少了一步赋值给parent的孩子的步骤而已;如果是第二种情况(cur既有左孩子,又有右孩子),我们一样可以向上面那样做,只不过最后不把cur.right赋值给parent的孩子了,直接返回即可。

总结上面的1,2,我们可以最后得到三种情况

  1. 如果二叉搜索树中没有key对应的值:直接返回key即可
  2. cur有左孩子,但没有右孩子
  3. cur既有左孩子,又有右孩子

为了能让代码简洁和封装性,我们可以声明一个函数,来处理删除结点的操作,然后在外面判断cur是否为根节点。

代码如下:

/*删除单个节点的函数*/
TreeNode* deleteOneNode(TreeNode* target) {
	if (target == nullptr) return target; //1.节点为空,对应于没找到key对应的target
	if (target->right == nullptr) return target->left;//2.target没有右孩子,直接返回左子树即可
	//3.target有右孩子,需要先找到target的右子树的最左边节点,然后进行树的拼接
	TreeNode* cur = target->right;
	// 找target右子树的最左边节点
	while (cur->left) {
		cur = cur->left;
	}
	// 把target的左子树拼接到最左边节点的左子树
	cur->left = target->left;
	// 返回target的右子树
	return target->right;
}

TreeNode* deleteNode(TreeNode* root, int key) {
	if (root == nullptr) return root;
	TreeNode* cur = root;
	TreeNode* pre = nullptr; // 记录cur的父节点,用来删除cur

	// 首先先从二叉搜索树中找到该节点
	while (cur) {
	    if (cur->val == key) break;
	    pre = cur;
	    if (cur->val > key) cur = cur->left;
	    else cur = cur->right;
	}
	// 如果key对应的cur是根节点
	if (pre == nullptr) {
	    return deleteOneNode(cur);  //直接返回即可,不需要赋值
	}
	// 如果cur是pre的左孩子
	if (pre->left && pre->left->val == key) {
	    pre->left = deleteOneNode(cur); //拼接到pre的左子树,要给pre->left赋值
	}
	// 如果cur是pre的右孩子
	if (pre->right && pre->right->val == key) {
	    pre->right = deleteOneNode(cur);//拼接到pre的右子树,要给pre->right赋值
	}
	return root;
	}

2.总结

算法

二叉搜索树的删除操作要考虑很多情况,在考虑情况的时候,我们要先从删除节点(这道题的主题)入手,然后再去考虑一些细节方面的问题(如key对应的就是根节点)。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值