二叉树《学习笔记》——删除操作

376 阅读4分钟

这是我参与8月更文挑战的第9天,活动详情查看:8月更文挑战

删除二叉树的子节点

这个比较复杂一点,要分情况进行处理。首先找到要删除的节点,没找到就不用删除了;处理方式如下:

首先定义一些临时的变量,将查找的key和当前结点的key值进行比较,如果较小就往左子树进行查找,如果较大就往右子树进行查找,然后再通过循环来找要删除的节点;

 var current = this.root;
 var parent = null;
 var isLeftChild = true
 while (current.key != key) {
     parent = current;
     if (key < current.key) {
         isLeftChild = true;
         current = current.left;
     } else {
         isLeftChild = false;
         current = current.right;
     }
     if (current == null) return false;
 }

找到后一共有三种情况:

一、删除叶子节点

查找当前current节点的left和right是否为空,且不是根节点,直接让它们指向null即可;如果是根节点的话,那么就相当于清空二叉树。

if (current.left == null && current.right == null) {
    if (current == this.root) {
        this.root = null;
    } else if (isLeftChild) {
        parent.left = null;
    } else {
        parent.right = null;
    }
}

二、删除只有一个子节点的节点

这种情况也要分成两种情况考虑,这个子节点可能是左子节点,也可能是右子节点;不过这两种情况的思路是相同的,分别进行处理就好了;下面说要删除的节点有一个左子节点的情况,右侧同理.

  1. 判断是否为根节点

    不管是在哪种情况,都要记得首先要判断这个要删除的节点是不是根节点;因为如果是根节点的话,与其他节点位置的处理方式是不相同的。如果是根节点,直接重新让这个要删除的子节点为根节点即可;

  2. 判断要删除的节点在其父节点的左侧还是右侧

    该删除节点为左节点或右节点也影响了要将其子节点放在父节点的位置,如果当前节点是其父节点的左子节点,那么就要将当前节点的子节点连到其父节点的左子节点上;右侧同理.

if (current.right == null) {
    if (current == this.root) {
        this.root = current.left;
    } else if (isLeftChild) {
        parent.left = current.left;
    } else {
        parent.right = current.left
    }
}

三、删除有两个子节点的节点

这种情况是最复杂的....(想想就很复杂)这种情况也分为多种情况,不管在哪一种情况中,我们都需要在删除节点current的子节点中找一个代替它的节点,而且这个替代它的节点要跟current节点最接近;那么要怎么样才是最接近的呢?

那就是current左子树中的最大值,以及current右子树的最小值,也就是要往current左子树的最右边进行查找直到这个节点指向为空,或者往current的右子树的最左边进行查找直到这个节点指向为空;此时找到的这个节点就是可以替代current节点。

比如说下面这一张图,如果要删除15这个节点,可以找14或者18来代替它;

二叉搜索树.png

前驱&后继:比current小一点点的节点称为current节点的前驱;比current大一点点的节点称为current节点的后继;也就是说,在我们要是删除current的是否,就是要找到它的前驱或者后继;

下面用以找后继为例子:

  1. 首先要找后继

    通过循环来查找它的后继,这个后继可能是current的儿子,也可能隔了好几代,如果是隔了好几代的话,在替代的时候就要让这个后继节点指向原来current节点的右子树;

    并且,如果这个后继节点还有儿子的话,也要让这个后继节点的父节点指向这个后继节点的子节点;就比如上面的图中,让18这个节点替代20的时候,不仅要让18指向20的子树,同时也要让20指向18的子节点19;

    var successor = delNode;
    var current = delNode.right;
    var successorParent = delNode;
    while (current != null) {
        successorParent = successor;
        successor = current;
        current = current.left
    }
    // 3. 判断寻找的后继节点是否为delNode的right节点
    if (successor != delNode.right) {
        successorParent.left = successor.right;
        successor.right = delNode.right
    }
    return successor;
    
  2. 把原来current的父节点和左子树给后继节点successor

    首先也要判断这个节点是不是根节点,是的话就直接让根节点指向这个后继节点successor;不是的话就判断此时current是位于左子树的位置还是右子树,让current的父节点指向successor;再让把current的左子树赋值给给successor

    if (current == this.root) {
        this.root = successor;
    } else if (isLeftChild) {
        parent.left = successor;
    } else {
        parent.right = successor;
    }
    successor.left = current.left;