红黑树是一个平衡的二叉树,但不是一个完美的平衡二叉树。虽然我们希望一个所有查找都能在 lgN 次比较内结束,但是这样在动态插入中保持树的完美平衡代价太高,所以,我们稍微放松一下限制,希望找到一个能在对数时间内完成查找的数据结构。这个时候,红黑树站了出来。
红黑树需要满足的五条性质:
- 节点是红色或者是黑色;
- 根节点是黑色;
- 每个叶节点(NIL或空节点)是黑色;
- 每个红色节点的两个子节点都是黑色的(也就是说不存在两个连续的红色节点);
- 从任一节点到其每个叶节点的所有路径都包含相同数目的黑色节点;
当我们进行插入或者删除操作通过变色、旋转来调整树使之符合这五条性质。 旋转的目的是将节点多的一支出让节点给另一个节点少的一支,旋转操作在插入和删除操作中经常会用到,所以要熟记。旋转又分为左旋和右旋。
左旋 右旋
1. 插入过程
我们先明确一下各节点的叫法:
因为要满足红黑树的这五条性质,如果我们插入的是黑色节点,那就违反了性质五,需要进行大规模调整,如果我们插入的是红色节点,那就只有在要插入节点的父节点也是红色的时候违反性质四或者是当插入的节点是根节点时,违反性质二,所以,我们把要插入的节点的颜色变成红色。下面是可能遇到的插入的几种状况:
1. 当插入的节点是根节点时,直接涂黑即可;
2. 当要插入的节点的父节点是黑色的时候。这个时候插入一个红色的节点并没有对这五个性质产生破坏。所以直接插入不用在进行调整操作。
3. 如果要插入的节点的父节点是红色且父节点是祖父节点的左支的时候。这个要分两种情况:
- 叔叔节点为黑的情况,
- 叔叔节点为红的情况。
当叔叔为黑时,也分为两种情况,一种是要插入的节点是父节点的左支,另一种是要插入的节点是父亲的右支。我们先看一下当要插入的节点是父节点的左支的情况:这个时候违反了性质四,我们就需要进行调整操作,使之符合性质四,我们可以通过对祖父节点进行右旋同时将祖父节点和父节点的颜色进行互换。经过这样的调整可以符合性质四并且不对其他性质产生破坏。
调整前 调整后
当插入的节点是父节点的右支的时候:当要插入的节点是父节点的右支的时候,我们可以先对父节点进行左旋,变成如下,如果我们把原先的父节点看做是新的要插入的节点,把原先要插入的节点看做是新的父节点,那就变成了当要插入的节点在父节点的左支的情况,对,是的,就是按照当要插入的节点在父节点的左支的情况进行旋转,旋转完之后变成如下:
4、如果要插入的节点的父节点是红色且父节点是祖父节点的右支的时候:这个时候的情况跟情况3所表述的情况是一个镜像,将情况3的左和右互换一下就可以了。
5、如果要插入的节点的父节点是红色并且叔叔节点也为红色:这个时候,只需将父亲节点和叔叔节点涂黑,将祖父节点涂红。
2. 删除
首先你要了解普通二叉树的删除操作:
- 如果删除的是叶节点,可以直接删除;
- 如果被以将子删除的元素有一个子节点,可节点直接移到被删除元素的位置;
- 如果有两个子节点,这时候就可以把被删除元素的右支的最小节点(被删除元素右支的最左边的节点)和被删除元素互换,我们把被删除元素右支的最左边的节点称之为后继节点(后继元素),然后在根据情况1或者情况2进行操作。如图:
加入颜色之后,被删除元素和后继元素互换只是值得互换,并不互换颜色,这个要注意。
下面开始讲一下红黑树删除的规则:
- 当被删除元素为红时,对五条性质没有什么影响,直接删除。
- 当被删除元素为黑且为根节点时,直接删除。
- 当被删除元素为黑,且有一个右子节点为红时,将右子节点涂黑放到被删除元素的位置,
4. 当被删除元素为黑,且兄弟节点为黑,兄弟节点两个孩子也为黑,父节点为红,此时,交换兄弟节点与父节点的颜色;NIL 元素是指每个叶节点都有两个空的,颜色为黑的NIL元素,需要他的时候就可以把它看成两个黑元素,不需要的时候可以忽 视他。
5. 当被删除元素为黑、并且为父节点的左支,且兄弟颜色为黑,兄弟的右支为红色,这个时候需要交换兄弟与父亲的颜色, 并把父亲涂黑、兄弟的右支涂黑,并以父节点为中心左转。如图:
6. 当被删除元素为黑、并且为父节点的左支,且兄弟颜色为黑,兄弟的左支为红色,这个时候需要先把兄弟与兄弟的左子节 点颜色互换,进行右转,然后就变成了规则5一样了,在按照规则5进行旋转。如图:
7. 当被删除元素为黑且为父元素的右支时,跟情况 5、情况 6 互为镜像。
8. 被删除元素为黑且兄弟节点为黑,兄弟节点的孩子为黑,父亲为黑,这个时候需要将兄弟节点变为红,再把父亲看做那个被 删除的元素(只是看做,实际上不删除),看看父亲符和哪一条删除规则,进行处理变化如图:
9. 当被删除的元素为黑,且为父元素的左支,兄弟节点为红色的时候,需要交换兄弟节点与父亲结点的颜色,以父亲结点进行 左旋,就变成了情况4,在按照情况四进行操作即可,变化如下:
有了二叉查找树、平衡树还需要红黑树的原因?
1、二叉查找树的缺点
二叉查找树有可能出现一种极端的情况:
这种情况也是满足二叉查找树的条件,然而,此时的二叉查找树已经近似退化为一条链表,这样的二叉查找树的查找时间复杂度顿时变成了 O(n),可想而知,我们必须不能让这种情况发生,为了解决这个问题,于是我们引申出了平衡二叉树。
2. 平衡二叉树
平衡二叉树就是为了解决二叉查找树退化成一颗链表而诞生了,平衡树具有如下特点:
- 具有二叉查找树的全部特性。
- 每个节点的左子树和右子树的高度差至多等于1。
平衡树基于这种特点就可以保证不会出现大量节点偏向于一边的情况了
3. 为什么有了平衡树还需要红黑树?
虽然平衡树解决了二叉查找树退化为近似链表的缺点,能够把查找时间控制在 O(logn),不过却不是最佳的,因为平衡树要求每个节点的左子树和右子树的高度差至多等于1,这个要求实在是太严了,导致每次进行插入/删除节点的时候,几乎都会破坏平衡树的第二个规则,进而我们都需要通过左旋和右旋来进行调整,使之再次成为一颗符合要求的平衡树。显然,如果在那种插入、删除很频繁的场景中,平衡树需要频繁着进行调整,这会使平衡树的性能大打折扣,为了解决这个问题,于是有了红黑树。
与平衡树不同的是,红黑树在插入、删除等操作,不会像平衡树那样,频繁着破坏红黑树的规则,所以不需要频繁着调整,这也是我们为什么大多数情况下使用红黑树的原因。不过,如果你要说,单单在查找方面的效率的话,平衡树比红黑树快。所以,我们也可以说,红黑树是一种不大严格的平衡树。也可以说是一个折中发方案。