数据结构11-二叉搜索数的删除

前面实现了二叉搜索树的添加以及二叉树的遍历,也知道了前驱节点后继节点的概念,现在就可以实现二叉搜索数的删除功能,实现一个完整的二叉搜索树了。

继续为二叉搜索树实现以下两个方法:

// 删除一个元素,即删除这个元素对应的节点
- (void)removeObject:(id)object {
    [self removeWithNode:[self nodeWithObject:object]];
}

// 删除一个节点
- (void)removeWithNode:(JKRBinaryTreeNode *)node {

}
复制代码

删除叶子节点

如上图,删除二叉树中的叶子节点,只需要移除该节点就可以,二叉树依然满足二叉搜索树的性质。

// 叶子节点既没有左子树也没有右子树
if (!node.left && !node.right) {
    // 如果是根节点就是直接让根节点为空
    if (!node.parent) {
        _root = nil;
    } else {
        // 如果是父节点的左子节点,就让父节点的左字节点为空
        if (node == node.parent.left) {
            node.parent.left = nil;
        } else {
            node.parent.right = nil;
        }
    }
}
复制代码

删除叶子节点的逻辑:

  1. 判断被删除节点如否是根节点,是进入第2步,否进入第3步。
  2. 直接将二叉树的根节点指向空
  3. 判断被删除节点是父节点的左子节点还是右子节点,左子节点进入第4步,右子节点进入第5步
  4. 将断被删除节点的父节点的左子节点置空
  5. 将断被删除节点的父节点的右子节点置空

根据如上逻辑,删除二叉树中的 5 和 8:

[tree removeObject:@5];

                ┌-7 (p: (null))-┐
                │               │
          ┌-4 (p: 7)       ┌-9 (p: 7)-┐
          │                │          │
    ┌-2 (p: 4)-┐       8 (p: 9) ┌-11 (p: 9)-┐
    │          │                │           │
1 (p: 2)    3 (p: 2)       10 (p: 11)   12 (p: 11)

[tree removeObject:@8];

                ┌-7 (p: (null))-┐
                │               │
          ┌-4 (p: 7)         9 (p: 7)-┐
          │                           │
    ┌-2 (p: 4)-┐                ┌-11 (p: 9)-┐
    │          │                │           │
1 (p: 2)    3 (p: 2)       10 (p: 11)   12 (p: 11)
复制代码

删除度为1的节点

如上图,删除度为1节点后,非常类似于删除链表的节点的操作,只需要将节点的父节点指向它的指针指向它自己的字节点就可以:

if ((node.left && !node.right) || (node.right && !node.left)) {
    JKRBinaryTreeNode *replacement = node.left ? node.left : node.right;
    // 一定不要忘记度parent的维护
    replacement.parent = node.parent;
    if (!node.parent) {
        _root = replacement;
    } else if(node == node.parent.left) {
        node.parent.left = replacement;
    } else {
        node.parent.right = replacement;
    }
}
复制代码

删除度为1的节点的逻辑:

  1. 判断被删除节点如否是根节点,是进入第2步,否进入第3步
  2. 被删除节点的度为1,只有一个子节点,获取被删除节点的子节点
  3. 将被删除节点的子节点的parent指向被删除节点的父节点
  4. 判断被删除节点是父节点的左子节点还是右子节点,左子节点进入第5步,右子节点进入第6步
  5. 将断被删除节点的父节点的左子节点指向被删除节点的子节点
  6. 将断被删除节点的父节点的右子节点指向被删除节点的子节点

根据如上逻辑,删除二叉树中的 4 和 9:

[tree removeObject:@4];

          ┌-7 (p: (null))-┐
          │               │
    ┌-2 (p: 7)-┐       9 (p: 7)-┐
    │          │                │
1 (p: 2)    3 (p: 2)      ┌-11 (p: 9)-┐
                          │           │
                     10 (p: 11)   12 (p: 11)

[tree removeObject:@9];

          ┌----7 (p: (null))----┐
          │                     │
    ┌-2 (p: 7)-┐          ┌-11 (p: 7)-┐
    │          │          │           │
1 (p: 2)    3 (p: 2) 10 (p: 11)   12 (p: 11)
复制代码

一定不要忘记对parent的维护。

删除度为2的节点

二叉搜索树的中度为2的节点如下图:

可以看到这些节点都有两个子节点,即同时存在左子树和右子树,这些节点不能直接移除,也不能直接让它的一个子节点替代它的位置,但是可以根据如下方法删除:

  • 找到被删除节点的后继节点(或前驱节点)做为替代节点
  • 将替代节点的保存的元素移动到被删除节点中
  • 删除替代节点

比如删除上述二叉搜索树中的根节点 7 :

先找到7的后继节点做为替代的节点

将后继节点的值移动到根节点上

删除后继节点:

可以看到,这样操作之后,二叉树还是一棵二叉搜索树。

这里以后继节点来分析,前驱节点同理。后继节点是中序遍历时,一个节点的后一个节点,二叉搜索树的中序遍历又好是升序排列。所以在二叉搜索树中的,一个节点的后继节点的元素刚按照升序排列的后一个元素。

二叉搜索树中一个度为2的节点,它的后继节点一定在它的右子树中,且一定不是度为2的节点,它的度只能是1或者0。

原因:

  • 二叉搜索树中序遍历刚好是升序,所以它的后继节点一定是按照升序排列后一个节点
  • 二叉搜索树任意节点的左子树所有元素都比它小,右子树所有元素都比它大
  • 所以一个度2的节点的后继节点,一定在它的右子树中,而且是它的右子树中所有元素最小的
  • 所以一个度为2的节点的后继节点,一定不会存在左子树。因为假如它有左子树,它就不是最小的。

综上逻辑找到一个二叉搜索树节点的前驱节点或后继节点如下:

#pragma mark - 节点的前驱节点
- (JKRBinaryTreeNode *)predecessorWithNode:(JKRBinaryTreeNode *)node {
    if (!node) {
        return nil;
    }
    // 节点有左子树的情况下,前驱节点在它的左子树中
    if (node.left) {
        // 前驱节点是 node.left.right.right...
        JKRBinaryTreeNode *p = node.left;
        while (p.right) {
            p = p.right;
        }
        return p;
    }
    // 节点没有左子树的情况下,如果有父节点,在父节点往上找
    while (node.parent && node == node.parent.left) {
        node = node.parent;
    }
    
    // 没有左子树,也没有父节点,就没有前驱节点
    // !node.left && (!node.parent || node == node.parent.right)
    return node.parent;
}

#pragma mark - 节点的后继节点
- (JKRBinaryTreeNode *)successorWithNode:(JKRBinaryTreeNode *)node {
    if (!node) {
        return nil;
    }
    
    if (node.right) {
        JKRBinaryTreeNode *p = node.right;
        while (p.left) {
            p = p.left;
        }
        return p;
    }
    
    while (node.parent && node == node.parent.right) {
        node = node.parent;
    }
    
    return node.parent;
}
复制代码

而删除度为2节点,只需要先找到这个节点后继节点,然后将后继节点的值移动后,直接利用上面删除度为0或者1的节点逻辑就可以:

if (node.left && node.right) {
    JKRBinaryTreeNode *s = [self successorWithNode:node];
    node.object = s.object;
    node = s;
}

if ((node.left && !node.right) || (node.right && !node.left)) {
    JKRBinaryTreeNode *replacement = node.left ? node.left : node.right;
    // 一定不要忘记度parent的维护
    replacement.parent = node.parent;
    if (!node.parent) {
        _root = replacement;
    } else if(node == node.parent.left) {
        node.parent.left = replacement;
    } else {
        node.parent.right = replacement;
    }
}

if (!node.left && !node.right) {
    // 如果是根节点就是直接让根节点为空
    if (!node.parent) {
        _root = nil;
    } else {
        // 如果是父节点的左子节点,就让父节点的左字节点为空
        if (node == node.parent.left) {
            node.parent.left = nil;
        } else {
            node.parent.right = nil;
        }
    }
}
复制代码

将如下二叉搜索树依次进行删除操作:

                ┌---7 (p: (null))---┐
                │                   │
          ┌-4 (p: 7)-┐         ┌-9 (p: 7)-┐
          │          │         │          │
    ┌-2 (p: 4)-┐  5 (p: 4) 8 (p: 9) ┌-11 (p: 9)-┐
    │          │                    │           │
1 (p: 2)    3 (p: 2)           10 (p: 11)   12 (p: 11)

[tree removeObject:@7];

                ┌-8 (p: (null))-┐
                │               │
          ┌-4 (p: 8)-┐       9 (p: 8)-┐
          │          │                │
    ┌-2 (p: 4)-┐  5 (p: 4)      ┌-11 (p: 9)-┐
    │          │                │           │
1 (p: 2)    3 (p: 2)       10 (p: 11)   12 (p: 11)

[tree removeObject:@4];

                ┌-8 (p: (null))-┐
                │               │
          ┌-5 (p: 8)         9 (p: 8)-┐
          │                           │
    ┌-2 (p: 5)-┐                ┌-11 (p: 9)-┐
    │          │                │           │
1 (p: 2)    3 (p: 2)       10 (p: 11)   12 (p: 11)

[tree removeObject:@8];

                ┌-9 (p: (null))-┐
                │               │
          ┌-5 (p: 9)      ┌-11 (p: 9)-┐
          │               │           │
    ┌-2 (p: 5)-┐     10 (p: 11)   12 (p: 11)
    │          │
1 (p: 2)    3 (p: 2)
复制代码

删除代码汇总

将上面的三种情况代码进行合并:

- (void)removeWithNode:(JKRBinaryTreeNode *)node {
    if (!node) {
        return;
    }
    _size--;
    
    if (node.hasTwoChildren) {
        JKRBinaryTreeNode *s = [self successorWithNode:node];
        node.object = s.object;
        node = s;
    }
    
    // 实际被删除节点的子节点
    JKRBinaryTreeNode *replacement = node.left ? node.left : node.right;
    if (replacement) { // 被删除的节点度为1
        replacement.parent = node.parent;
        if (!node.parent) {
            _root = replacement;
        } else if (node == node.parent.left) {
            node.parent.left = replacement;
        } else {
            node.parent.right = replacement;
        }
    } else {
        if(!node.parent) { // 被删除的节点度为0且没有父节点,被删除的节点是根节点且二叉树只有一个节点
            _root = nil;
        } else { // 被删除的节点是叶子节点且不是根节点
            if (node.isLeftChild) {
                node.parent.left = nil;
            } else {
                node.parent.right = nil;
            }
        }
    }
}
复制代码

源码

点击查看源码

转载于:https://juejin.im/post/5d076449e51d455a2f220266

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值