为何有红黑树?什么是红黑树?看完这篇你就明白了

为何要有红黑树

想必你们对二叉树搜索树都不陌生,首先看一下二叉搜索树的定义:
二叉搜索树(Binary Search Tree),或者是一棵空树,或者是具备下列性质的二叉树:若它的左子树不空,则左子树上全部结点的值均小于它的根结点的值;若它的右子树不空,则右子树上全部结点的值均大于它的根结点的值;它的左、右子树也分别为二叉排序树。
从理论上来讲,二叉搜索树的查询、插入和删除一个节点的时间复杂度均为O(log(n)),已经彻底能够知足咱们的要求了,那么为何还要有红黑树呢?
咱们来看一个例子,向二叉搜索树中依次插入(1,2,3,4,5,6),插入以后是这样的算法

退化成链表的二叉搜索树
能够看到,在这种状况下,二叉搜索树退化成了链表!!!这时候查询、插入和删除一个元素的时候,时间复杂度变成了O(n),显然这是不能接受的。出现这种状况状况的缘由是二叉搜索树没有自平衡的机制,因此就有了平衡二叉树的概念。
平衡二叉树(Balanced Binary Tree)具备如下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,而且左右两个子树都是一棵平衡二叉树。
仍是刚刚的例子,假如咱们用平衡二叉树来实现的话,插入完元素后应该是下面这样的(不惟一)
自平衡的二叉树
平衡二叉树保证了在最差的状况下,二叉树依然可以保持绝对的平衡,即左右两个子树的高度差的绝对值不超过1。可是这又会带来一个问题,那就是平衡二叉树的定义过于严格,致使每次插入或者删除一个元素以后,都要去维护二叉树总体的平衡,这样产生额外的代价又太大了。二叉搜索树可能退化成链表,而平衡二叉树维护平衡的代价开销又太大了,那怎么办呢?这就要谈到“中庸之道”的智慧了。说白了就是把平衡的定义适当放宽,不那么严格,这样二叉树既不会退化成链表,维护平衡的开销也能够接受。没错,这就是咱们要谈的红黑树了。首先看一下红黑树的定义:
红黑树是一种含有红黑结点并能自平衡的二叉查找树。它必须除了知足二叉搜索树的性质外,还要知足下面的性质:
性质1:每一个节点要么是黑色,要么是红色。
性质2:根节点是黑色。
性质3:每一个叶子节点(NIL)是黑色。
性质4:每一个红色结点的两个子结点必定都是黑色。
性质5:任意一结点到每一个叶子结点的路径都包含数量相同的黑结点。
这就是红黑树的五条性质。我相信不少人都看到过,能背下来的也不在少数,可是真正理解为何要这样定义的恐怕就很少了。下面就从2-3树的角度来谈谈红黑树的定义。

从2-3树来看红黑树

通常咱们接触最多的是二叉树,也就是一个父节点最多有两个子节点。2-3树与二叉树的不一样之处在于,一个父节点能够有两个子节点,也能够有三个子节点,而且其也知足相似二叉搜索树的性质。还有最重要的,2-3树的全部叶子节点都在同一层,且最后一层不能有空节点,相似于满二叉树。
咱们依次插入10,9,8,7,6,5,4,3,2,1来看一下2-3数是如何进行自平衡的。
2-3树在插入元素以前首先要进行一次未命中的查找,而后将元素插入叶子节点中,以后再进行平衡操做,下面具体说明。
首先插入10,以下图编程

2-3树中插入10

而后插入9,9小于10,2-3树在插入时要将9融入10这个叶子节点中(固然也是根节点),融合完成后以下:
2-3树中插入9

这是一个3节点,不用执行平衡操做。2-3树中把有两个元素,三个子节点的节点称为3节点,把有一个元素,两个子节点的的节点称为2节点。
接着插入8,插入8的时候一样要先融入叶子节点中,以下图左侧所示
2-3树中插入8

8融入叶子节点后,该结点便拥有了3个元素,不知足2-3树的定义,这时就要把3节点进行分裂,即把左侧和右侧的元素分裂为2节点,而中间的元素抽出,继续融入其父节点,在这里便成为了一个根节点。
继续插入7,以下
2-3树中插入7
插入后,各个节点都知足2-3树的定义,不须要进行平衡操做。
接着插入6,仍是首先找到叶子节点,而后将其融入,以下图左侧所示
2-3树中插入6
插入后六、七、8三个元素所在的叶子节点再也不知足2-3树的定义,须要进行分裂,即抽出元素7融入父节点,6和8分裂为7的左右子节点。
接着插入5,以下图所示,一样不须要进行平衡操做
2-3树中插入5
接着插入4,仍是首先找到叶子节点,而后将其融入,以下图左侧所示
2-3树中插入4
插入后四、五、6三个元素所在的叶子节点再也不知足2-3树的定义,须要进行分裂,即抽出元素5融入父节点,4和6分裂为5的左右子节点。5融入父节点后,该结点便有了五、七、9三个元素,于是须要继续分裂,元素7成为新的根节点,5和9成为7的左右子节点。
接着插入3,3融入4所在的叶子节点中,不须要进行平衡操做
2-3树中插入3
接着插入2,仍是首先找到叶子节点,而后将其融入,以下图左侧所示
2-3树中插入2
插入后二、三、4三个元素所在的叶子节点再也不知足2-3树的定义,须要进行分裂,即抽出元素3融入父节点,2和4分裂为3的左右子节点,3融入5所在的父节点中。
最后插入2,一样先找到叶子节点,而后将其融入,以下图所示
2-3树中插入1
至此,咱们便完成了在2-3树中依次插入10,9,8,7,6,5,4,3,2,1,而且2-3树始终维护着平衡。怎么样,是否是很神奇。

再看红黑树

那么红黑树与2-3树有什么关系呢?如今咱们对2-3树进行改造,改形成一个二叉树。怎么改造呢?对于2节点,保持不变;对于3节点,咱们首先将3节点中左侧的元素标记为红色,以下图2所示。
spa

2-3树到红黑树的改造
而后咱们将其改形成图3的形式;再将3节点的位于中间的子节点的父节点设置为父节点中那个红色的节点,如图4的所示;最后咱们将图4的形式改成二叉树的样子,如图5所示。图5是否是很熟悉,没错,这就是咱们经常提到的大名鼎鼎的红黑树了。
下面咱们回过头再看下红黑树的五条性质。
从2-3树看红黑树
性质1:每一个节点要么是黑色,要么是红色。
2-3树中存在2节点和3节点,3节点中左侧的元素即是红色节点,而其余的节点即是黑色节点。
性质2:根节点是黑色。
在2-3树中,根节点只能是2节点或者3节点,2节点与3节点在红黑树中的等价形式,以下图所示
2节点与3节点在红黑树中的等价形式
显然,不管是哪一种状况,根节点都是黑色的。
性质3:每一个叶子节点(NIL)是黑色。
这里的叶子节点不是指左右子树为空的那个叶子节点,而是指节点不存在子节点或者为空节点被称做叶子节点。在性质2中咱们讨论的根节点是黑色的都是讨论根节点不为空的状况,若红黑树是一个空树,那么根节点天然也是空的叶子节点,这时候叶子节点便必然是黑色的。
性质4:每一个红色结点的两个子结点必定都是黑色。
仍是从2-3树的角度来理解,红色节点对应2-3树中3节点左侧的元素,那么它的子节点要么是2节点,要么是3节点。不管是2节点仍是3节点对应的节点颜色都是黑色的,这在性质2时已经讨论了。
性质5:任意一结点到每一个叶子结点的路径都包含数量相同的黑结点。
性质5应该是红黑树最重要的一条性质了。2-3树是一颗绝对平衡的树,即2-3树中任意一个节点出发,到达叶子节点后所通过的节点数都是同样的。那么对应到红黑树呢?2-3树中2节点对应到红黑树即是一个黑色的节点,而3节点对应到红黑树是一个红色节点和一个黑色节点。因此,不管是2节点仍是3节点,在红黑树中都会对应一个黑色节点。那么2-3树中的绝对平衡,在红黑树中天然就是任意一结点到每一个叶子结点的路径都包含数量相同的黑结点了。
相信你们如今已经对红黑树的五条性质有了更加深入的体会了。那么咱们再看下红黑树维持平衡的三种操做,即变色、左旋、右旋怎么理解呢?
首先看一下 变色,如下图为例,
红黑树的变色
在2-3树中插入节点3后,便再也不知足2-3树的定义,须要进行分解,将元素2抽出做为1和3的父节点,而后2继续向上融合。
对应到红黑树中就是,首先插入节点3,在红黑树中新插入的节点默认为红色,而后不知足定义,因此须要进行分解,分解后各个节点都为2节点,因此变为黑色。而2节点须要继续向上融合,故要变成红色。
接着看一下 右旋转,如下图为例,
红黑树的右旋转
插入元素1后,进行右旋转操做,首先把2节点与3节点断开链接,同时把2与2的右子树断开链接,而后把2的右子树链接至3的左子树位置,不会违背二分搜索树的性质,而后再把3链接至2的右子树位置。最后还要改变对应节点的颜色,即把2节点的颜色改成原来3节点的黑色,把3节点的颜色改成原来2节点的红色。
接着看一下 左旋转,与右旋转相似,如下图为例,
红黑树的左旋转
插入元素3后,进行左旋转操做,首先把2节点与3节点断开链接,同时把3与3的左子树断开链接,而后把3的左子树链接至2的右子树位置,不会违背二分搜索树的性质,而后再把2链接至3的左子树位置。最后还要改变对应节点的颜色,即把2节点的颜色改成原来3节点的红色,把3节点的颜色改成原来2节点的黑色。

写在最后

最后须要说的是,本文中提到的红黑树是一种特殊的红黑树——左倾红黑树,即红色节点都是父节点的左子树,其实按照红黑树的定义没必要这样。只要知足红黑树的五条性质,就是红黑树,好比彻底能够实现右倾红黑树等等,但愿你们不要有误解。
更多关于红黑树的知识,好比红黑树的插入、删除操做,限于篇幅,本文再也不介绍,有兴趣的仍是推荐你们阅读《算法4》或者《算法导论》。
更多关于算法、数据机构和计算机基础知识的内容,欢迎扫码你们关注个人公众号“超悦编程”。3d

超悦编程