红黑树学习及Java实现

作者: can_4999 | 来源:发表于2019-11-10 22:24 被阅读0次

    BST

    二叉查找树(Binary Search Tree,简称BST)是一棵二叉树,它的左子节点的值比父节点的值要小,右节点的值要比父节点的值大。它的高度决定了它的查找效率。

    image

    在理想的情况下,二叉查找树增删查改的时间复杂度为O(logN)(其中N为节点数),最坏的情况下为O(N)。当它的高度为logN+1时,我们就说二叉查找树是平衡的。

    BST存在的问题

    BST存在的主要问题是,数在插入的时候会导致树倾斜,不同的插入顺序会导致树的高度不一样,而树的高度直接的影响了树的查找效率。理想的高度是logN,最坏的情况是所有的节点都在一条斜线上,这样的树的高度为N。

    RBTree

    基于BST存在的问题,一种新的树——平衡二叉查找树(Balanced BST)产生了。平衡树在插入和删除的时候,会通过旋转操作将高度保持在logN。其中两款具有代表性的平衡树分别为AVL树和红黑树。AVL树由于实现比较复杂,而且插入和删除性能差,在实际环境下的应用不如红黑树。

    红黑树(Red-Black Tree,以下简称RBTree)的实际应用非常广泛,比如Linux内核中的完全公平调度器、高精度计时器、ext3文件系统等等,各种语言的函数库如Java的TreeMap和TreeSet,C++ STL的map、multimap、multiset等。

    RBTree也是函数式语言中最常用的持久数据结构之一,在计算几何中也有重要作用。值得一提的是,Java 8中HashMap的实现也因为用RBTree取代链表,性能有所提升。

    RBTree的定义

    RBTree的定义如下:

    1. 任何一个节点都有颜色,黑色或者红色

    2. 根节点是黑色的

    3. 父子节点之间不能出现两个连续的红节点,但是可以出现连续的黑色节点

    4. 任何一个节点向下遍历到其子孙的叶子节点,所经过的黑节点个数必须相等

    5. 空节点被认为是黑色的

    RBTree在理论上还是一棵BST树,但是它在对BST的插入和删除操作时会维持树的平衡,即保证树的高度在[logN,logN+1](理论上,极端的情况下可以出现RBTree的高度达到2*logN,但实际上很难遇到)。这样RBTree的查找时间复杂度始终保持在O(logN)从而接近于理想的BST。RBTree的删除和插入操作的时间复杂度也是O(logN)。RBTree的查找操作就是BST的查找操作。

    RBTree的旋转操作

    旋转操作(Rotate)的目的是使节点颜色符合定义,让RBTree的高度达到平衡。

    Rotate分为left-rotate(左旋)和right-rotate(右旋),区分左旋和右旋的方法是:待旋转的节点从左边上升到父节点就是右旋,待旋转的节点从右边上升到父节点就是左旋。即,左旋节点的父节点点从左边下沉成为子节点为左旋,右旋节点的父节点从右边下沉成为子节点为右旋

    image

    RBTree的查找操作

    参考树:二叉树

    RBTree的插入操作

    RBTree的插入与BST的插入方式是一致的,只不过是在插入过后,可能会导致树的不平衡,这时就需要对树进行旋转操作和颜色修复(在这里简称插入修复),使得它符合RBTree的定义。

    新插入的节点是红色的,插入修复操作如果遇到父节点的颜色为黑则修复操作结束。也就是说,只有在父节点为红色节点的时候是需要插入修复操作的。

    插入修复操作分为以下的三种情况,而且新插入的节点的父节点都是红色的:

    叔叔节点也为红色。

    叔叔节点为空,且祖父节点、父节点和新节点处于一条斜线上。

    叔叔节点为空,且祖父节点、父节点和新节点不处于一条斜线上。

    插入操作-case 1

    case 1的操作是将父节点和叔叔节点与祖父节点的颜色互换,这样就符合了RBTRee的定义。即维持了高度的平衡,修复后颜色也符合RBTree定义的第三条。下图中,操作完成后A节点变成了新的节点。如果A节点的父节点不是黑色的话(所以需要向上遍历所有节点颜色),则继续做修复操作。

    image

    插入操作-case 2

    case 2的操作是将B节点进行右旋操作,并且和父节点A互换颜色。通过该修复操作RBTRee的高度和颜色都符合红黑树的定义。如果B和C节点都是右节点的话,只要将操作变成左旋就可以了。

    image

    插入操作-case 3

    case 3的操作是将C节点进行左旋,这样就从case 3转换成case 2了,然后针对case 2进行操作处理就行了。case 2操作做了一个右旋操作和颜色互换来达到目的。如果树的结构是下图的镜像结构,则只需要将对应的左旋变成右旋,右旋变成左旋即可。

    image

    插入操作的总结

    插入后的修复操作是一个向root节点回溯的操作,一旦牵涉的节点都符合了红黑树的定义,修复操作结束。之所以会向上回溯是由于case 1操作会将父节点,叔叔节点和祖父节点进行换颜色,有可能会导致祖父节点不平衡(红黑树定义3)。这个时候需要对祖父节点为起点进行调节(向上回溯)。

    祖父节点调节后如果还是遇到它的祖父颜色问题,操作就会继续向上回溯,直到root节点为止,根据定义root节点永远是黑色的。在向上的追溯的过程中,针对插入的3中情况进行调节。直到符合红黑树的定义为止。直到牵涉的节点都符合了红黑树的定义,修复操作结束。

    如果上面的3中情况如果对应的操作是在右子树上,做对应的镜像操作就是了。

    删除节点

    删除操作首先需要做的也是BST的删除操作,删除操作会删除对应的节点,如果是叶子节点就直接删除,如果是非叶子节点,会用对应的中序遍历后继节点来顶替要删除节点的位置。删除后就需要做删除修复操作,使的树符合红黑树的定义,符合定义的红黑树高度是平衡的。

    删除修复操作在遇到被删除的节点是红色节点或者到达root节点时,修复操作完毕。

    删除修复操作是针对删除黑色节点(叶子节点为红色节点)才有的,当黑色节点被删除后会让整个树不符合RBTree的定义的第四条。需要做的处理是从兄弟节点上借调黑色的节点过来,如果兄弟节点没有黑节点可以借调的话,就只能往上追溯,将每一级的黑节点数减去一个,使得整棵树符合红黑树的定义。

    删除操作的总体思想是从兄弟节点借调黑色节点使树保持局部的平衡,如果局部的平衡达到了,就看整体的树是否是平衡的,如果不平衡就接着向上追溯调整。

    代码引申

    CompareTo 和 equals 和 == 区别

    CompareTo

    CompareTo 比较两个类对象。就不能直接通过>,<进行比较,引用对象不是基本数据类型。两个对象进行比较返回 “0”是 相等,正数是 大于,负数是小于

    ==

    当复合数据类型用(==)进行比较,比较的是他们在内存中的存放地址。

    equals

    当复合数据类型之间进行equals比较时,这个方法的初始行为是比较对象在堆内存中的地址,但在一些诸如String,Integer,Date类中把Object中的这个方法覆盖了,作用被覆盖为比较内容是否相同。

    Java 中 比较两个类对象。就不能直接通过>,<进行比较,引用对象不是基本数据类型

    Java中很多类也都有CompareTo方法,甚至于排序算法的底层组成也是依赖于比较的,而这个比较就是依赖于各种数据类型的CompareTo或者Compare方法。Java中所有的compareTo方法都源于一个共同的接口,那就是Comparable。这个接口只有一个方法,那就是CompareTo。所有想要具有比较功能的类,都建议实现这个接口,而非是自己定义这个功能,这是面向对象的概念(将具有相同功能的事物抽象到一个共同的类或接口),并且为了多态也建议通过实现接口来进行向上转型,通过接口来操作具体实现,这也是面向接口编程要求我们做的。

    相关文章

      网友评论

        本文标题:红黑树学习及Java实现

        本文链接:https://www.haomeiwen.com/subject/qhfzbctx.html