红黑树和平衡二叉树有什么区别?

    科技2023-10-18  91

    目录

    二叉树(Binary Tree)

    二叉查找树(Binary Search Tree)

    红黑树(Red Black Tree)

    为什么工程中喜欢使用红黑树而不是其他二叉查找树?

    自平衡的红黑树

    左旋

    右旋


    二叉树(Binary Tree)

    是指每个节点最多只有两个分支的树结构,即不存在分支大于 2 的节点

    一棵空树或者满足以下性质的二叉树被称之为二叉查找树:

    若任意节点的左子树不为空,则左子树上所有节点的值均小于它的根节点的值;若任意节点的右子树不为空,则右子树上所有节点的值均大于或等于它的根节点的值;任意节点的左、右子树分别为二叉查找树。

    二叉查找树(Binary Search Tree)

    也被称为二叉搜索树、有序二叉树(Ordered Binary Tree)或排序二叉树(Sorted Binary Tree)等。

    红黑树(Red Black Tree)

    是一种自平衡二叉查找树,它最早被称之为“对称二叉 B 树”,它现在的名字源于 1978 年的一篇论文,之后便被称之为红黑树了。平衡二叉树可以有效的减少二叉树的深度,从而提高了查询的效率。

    红黑树除了具备二叉查找树的基本特性之外,还具备以下特性:

    节点是红色或黑色;根节点是黑色;所有叶子都是黑色的空节点(NIL 节点);每个红色节点必须有两个黑色的子节点,也就是说从每个叶子到根的所有路径上,不能有两个连续的红色节点;从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑色节点。

    为什么工程中喜欢使用红黑树而不是其他二叉查找树?

    红黑树的优势在于它是一个平衡二叉查找树,对于普通的二叉查找树(非平衡二叉查找树)在极端情况下可能会退化为链表的结构,例如,当我们依次插入 3、4、5、6、7、8 这些数据时,二叉树会退化为如下链表结构

    当二叉查找树退化为链表数据结构后,再进行元素的添加、删除以及查询时,它的时间复杂度就会退化为 O(n);而如果使用红黑树的话,它就会将以上数据转化为平衡二叉查找树,这样就可以更加高效的添加、删除以及查询数据了,这就是红黑树的优势。

    自平衡的红黑树

    红黑树能够实现自平衡和保持红黑树特征的主要手段是:变色、左旋和右旋。

    左旋

    指的是围绕某个节点向左旋转,也就是逆时针旋转某个节点,使得父节点被自己的右子节点所替代,如下图所示:

     

    在 TreeMap 源码中左旋的实现源码如下:

    // 源码基于 JDK 1.8 private void rotateLeft(Entry<K,V> p) { if (p != null) { // 右子节点 Entry<K,V> r = p.right; // p 节点的右子节点为 r 的左子节点 p.right = r.left; // r 左子节点如果非空,r 左子节点的父节点设置为 p 节点 if (r.left != null) r.left.parent = p; r.parent = p.parent; // r 父节点等于 p 父节点 // p 父节点如果为空,那么讲根节点设置为 r 节点 if (p.parent == null) root = r; // p 父节点的左子节点如果等于 p 节点,那么 p 父节点的左子节点设置 r 节点 else if (p.parent.left == p) p.parent.left = r; else p.parent.right = r; r.left = p; p.parent = r; } }

    左旋代码说明:在刚开始时,p 为父节点,r 为子节点,在左旋操作后,r 节点代替 p 节点的位置,p 节点成为 r 节点的左孩子,而 r 节点的左孩子成为 p 节点的右孩子。

    右旋

    指的是围绕某个节点向右旋转,也就是顺时针旋转某个节点,此时父节点会被自己的左子节点取代,如下图所示:

     在 TreeMap 源码中右旋的实现源码如下:

    private void rotateRight(Entry<K,V> p) {     if (p != null) {         Entry<K,V> l = p.left; // p 节点的左子节点为 l 的右子节点         p.left = l.right; // l 节点的右子节点非空时,设置 l 的右子节点的父节点为 p         if (l.right != null) l.right.parent = p;         l.parent = p.parent; // p 节点的父节点为空时,根节点设置成 l 节点         if (p.parent == null)             root = l; // p 节点的父节点的右子节点等于 p 节点时,p 的父节点的右子节点设置为 l         else if (p.parent.right == p)             p.parent.right = l;         else p.parent.left = l;         l.right = p;         p.parent = l;     } }

    右旋代码说明:在刚开始时,p 为父节点 l 为子节点,在右旋操作后,l 节点代替 p 节点,p 节点成为 l 节点的右孩子,l 节点的右孩子成为 p 节点的左孩子。

    Processed: 0.026, SQL: 8