数据结构与算法Java实现(5)——树(下)

AVL树
AVL树是高度平衡的而二叉树。它的特点是:AVL树中任何节点的两个子树的高度最大差别为1。
Alt
上面的两张图片,左边的是AVL树,它的任何节点的两个子树的高度差别都<=1;而右边的不是AVL树,因为7的两颗子树的高度相差为2(以2为根节点的树的高度是3,而以8为根节点的树的高度是1)。
关于AVL树点击此处
红黑树
Java中TreeMap的底层实现就是黑红树。
1.性质

性质1. 节点是红色或黑色。
性质2. 根节点是黑色。
性质3 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
性质4. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。

这些约束强制了红黑树的关键性质: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果是这个树大致上是平衡的。因为操作比如插入、删除和查找某个值的最坏情况时间都要求与树的高度成比例,这个在高度上的理论上限允许红黑树在最坏情况下都是高效的,而不同于普通的二叉查找树。
2.比较AVL树
AVL树是一棵严格的平衡树,它所有的子树都满足二叉平衡树的定义。因此AVL树高被严格控制在XXX,因此AVL树的查找比较高效。但AVL树插入、删除结点后旋转的次数比红黑树多。
红黑树用非严格的平衡来降低插入删除时旋转的次数。
因此,如果你的业务中查找远远多于插入、删除,那选AVL树;
如果查找、插入、删除频率差不多,那么选择红黑树。

/*RBTree是红黑树对应的类,RBTNode是红黑树的节点类。
在RBTree中包含了根节点mRoot和红黑树的相关API。*/
public class RBTree<T extends Comparable<T>> {

    private RBTNode<T> mRoot;    // 根结点

    private static final boolean RED   = false;
    private static final boolean BLACK = true;

    public class RBTNode<T extends Comparable<T>> {
        boolean color;        // 颜色
        T key;                // 关键字(键值)
        RBTNode<T> left;    // 左孩子
        RBTNode<T> right;    // 右孩子
        RBTNode<T> parent;    // 父结点

        public RBTNode(T key, boolean color, RBTNode<T> parent, RBTNode<T> left, RBTNode<T> right) {
            this.key = key;
            this.color = color;
            this.parent = parent;
            this.left = left;
            this.right = right;
        }

    }
}

3.插入过程
默认插入的结点为红色。为何?
因为红黑树中黑节点至少是红节点的两倍,因此插入节点的父节点为黑色的概率较大,而此时并不需要作任何调整,因此效率较高。
3.1. 父为黑
在这里插入图片描述
插入后无需任何操作。由于黑节点个数至少为红节点的两倍,因此父为黑的情况较多,而这种情况在插入后无需任何调整,这就是红黑树比AVL树插入效率高的原因!
3.2.父为红
父为红的情况破坏了红黑树的性质,此时需要根据叔叔的颜色来做不同的处理。
在这里插入图片描述
叔叔为红
在这里插入图片描述
此时很简单,只需交换爸爸、叔叔和爷爷的颜色即可。
此时若爷爷节点和太爷爷节点颜色相同,再以爷爷节点为起始节点,进行刚才相同的操作,即:根据爷爷的兄弟颜色做相应的操作。
叔叔为黑
此时较为复杂,分如下四种情况:
a)爸爸在左、叔叔在右、我在左
在这里插入图片描述
以爸爸为根节点,进行一次R旋转。
b)爸爸在左、叔叔在右、我在右
在这里插入图片描述
先以我为根节点,进行一次L旋转;
再以我为根节点,进行一次R旋转。
c)叔叔在左、爸爸在右、我在左
在这里插入图片描述
先以我为根节点,进行一次R旋转;
再以我为根节点,进行一次L旋转。
d)叔叔在左、爸爸在右、我在右
在这里插入图片描述
以爸爸为根节点,进行一次L旋转。

/* 
 * 将结点插入到红黑树中
 *
 * 参数说明:
 *     node 插入的结点        // 对应《算法导论》中的node
 */
private void insert(RBTNode<T> node) {
    int cmp;
    RBTNode<T> y = null;
    RBTNode<T> x = this.mRoot;

    // 1. 将红黑树当作一颗二叉查找树,将节点添加到二叉查找树中。
    while (x != null) {
        y = x;
        cmp = node.key.compareTo(x.key);
        if (cmp < 0)
            x = x.left;
        else
            x = x.right;
    }

    node.parent = y;
    if (y!=null) {
        cmp = node.key.compareTo(y.key);
        if (cmp < 0)
            y.left = node;
        else
            y.right = node;
    } else {
        this.mRoot = node;
    }

    // 2. 设置节点的颜色为红色
    node.color = RED;

    // 3. 将它重新修正为一颗二叉查找树
    insertFixUp(node);
}

/* 
 * 新建结点(key),并将其插入到红黑树中
 *
 * 参数说明:
 *     key 插入结点的键值
 */
public void insert(T key) {
    RBTNode<T> node=new RBTNode<T>(key,BLACK,null,null,null);

    // 如果新建结点失败,则返回。
    if (node != null)
        insert(node);
}

4.删除过程
实际删除节点要么是叶子节点,要么有且仅有一个左孩子;
若为叶子节点,必为红色;
若实际删除节点还有孩子,则该必为左孩子;
a)若左孩子为红色,则实际删除节点必为黑色;
b)若左孩子为黑色,则实际删除节点红黑均可以。
4.1. 父为红色(待删节点为叶子)
直接删除父节点即可:
在这里插入图片描述
4.2. 父为黑 子为红(待删节点为黑、待删节点子节点为红+左孩子)
用子节点覆盖父节点,并保持父节点的颜色:
在这里插入图片描述
4. 3父为黑 子为黑(待删节点和子节点均为黑)
4.3. 1叔叔为红
PS:叔叔为红,则爷爷必为黑!
1.父在左 叔在右
a)子节点覆盖父节点
b)进行一次左旋
在这里插入图片描述
2.父在右 叔在左
a)子节点覆盖父节点
b)进行一次右旋
4.3. 1叔叔为黑
PS:叔叔、爸爸都为黑,那爷爷颜色就不确定了!
4.3.1.1祖父红 两个侄子黑
以下两种情况操作一致:
1.子覆盖父(删除)
2.交换祖父和叔叔的颜色。
a)父在左 叔在右
在这里插入图片描述
b)父在右 叔在左
同上。
4.3.1.2祖父黑 两个侄子黑
以下两种情况操作一致:

  1. 祖父染成子节点的颜色;
  2. 子节点染成黑色;
  3. 叔叔染成红色
    a)父在左 叔在右
    在这里插入图片描述
    …可以看看这个
    代码实现
/* 
 * 删除结点(node),并返回被删除的结点
 *
 * 参数说明:
 *     node 删除的结点
 */
private void remove(RBTNode<T> node) {
    RBTNode<T> child, parent;
    boolean color;

    // 被删除节点的"左右孩子都不为空"的情况。
    if ( (node.left!=null) && (node.right!=null) ) {
        // 被删节点的后继节点。(称为"取代节点")
        // 用它来取代"被删节点"的位置,然后再将"被删节点"去掉。
        RBTNode<T> replace = node;

        // 获取后继节点
        replace = replace.right;
        while (replace.left != null)
            replace = replace.left;

        // "node节点"不是根节点(只有根节点不存在父节点)
        if (parentOf(node)!=null) {
            if (parentOf(node).left == node)
                parentOf(node).left = replace;
            else
                parentOf(node).right = replace;
        } else {
            // "node节点"是根节点,更新根节点。
            this.mRoot = replace;
        }

        // child是"取代节点"的右孩子,也是需要"调整的节点"。
        // "取代节点"肯定不存在左孩子!因为它是一个后继节点。
        child = replace.right;
        parent = parentOf(replace);
        // 保存"取代节点"的颜色
        color = colorOf(replace);

        // "被删除节点"是"它的后继节点的父节点"
        if (parent == node) {
            parent = replace;
        } else {
            // child不为空
            if (child!=null)
                setParent(child, parent);
            parent.left = child;

            replace.right = node.right;
            setParent(node.right, replace);
        }

        replace.parent = node.parent;
        replace.color = node.color;
        replace.left = node.left;
        node.left.parent = replace;

        if (color == BLACK)
            removeFixUp(child, parent);

        node = null;
        return ;
    }

    if (node.left !=null) {
        child = node.left;
    } else {
        child = node.right;
    }

    parent = node.parent;
    // 保存"取代节点"的颜色
    color = node.color;

    if (child!=null)
        child.parent = parent;

    // "node节点"不是根节点
    if (parent!=null) {
        if (parent.left == node)
            parent.left = child;
        else
            parent.right = child;
    } else {
        this.mRoot = child;
    }

    if (color == BLACK)
        removeFixUp(child, parent);
    node = null;
}

/* 
 * 删除结点(z),并返回被删除的结点
 *
 * 参数说明:
 *     tree 红黑树的根结点
 *     z 删除的结点
 */
public void remove(T key) {
    RBTNode<T> node; 

    if ((node = search(mRoot, key)) != null)
        remove(node);
}

参考:
https://blog.csdn.net/qq_34173549/article/details/79636764
https://www.cnblogs.com/skywang12345/p/3624343.html

猜你喜欢

转载自blog.csdn.net/weixin_43701058/article/details/90085371