小解红黑树
1.基本定义
首先,红黑树是一种特殊的二叉查找树(Binary Search Tree,也叫二叉搜索树)。一个BST是指:它是一颗空树,或者满足左子树(如果有)所有节点的值均小于根节点,右子树(如果有)所有节点的值都大于根节点,且左右子树均是二叉搜索树,这是一个递归定义。从BST的定义可以看出,其中序遍历序列是递增序列。
红黑树在BST定义的基础上,增加了几个属性:
key : 用于排序
parent : 指向父节点的引用
color:用于标记节点的颜色
此外,红黑树需要满足以下5个性质:
1)节点要么是红色,要么是黑色。
2)根节点是黑色。
3)叶子节点是黑色。这里的叶子节点指的是空节点(NIL节点)。
4)如果一个节点是红色,那么它的两个子节点都不能是红色。
5)从根节点到叶子节点的每一条路径,都包含相等数目的黑色节点。
其中4)和5)限制了红黑树任意两条路径的高度差不会大于最短的那条路径的高度,即最长路径的长度最多是最短路径的两倍,当一条路径全是黑色节点,另一条路径黑红相间时取到最大的差值。高度差这一点类似于平衡树,但红黑树不满足严格的二叉平衡树的定义,只是大致满足平衡性。同时这也使得红黑树的查找是O(lgn)级别的。
2.操作
红黑树的操作包括着色和旋转,之所以需要这些操作,是因为当进行红黑树的插入和删除操作时,有可能会破坏性质2、4和5,在这种情况下就需要调整相关节点使其重新满足红黑树的性质。
着色:即改变节点的颜色
旋转:旋转操作进行结构上的调整,包括左旋和右旋。左旋和右旋的原理十分相近,这里详细说明一下左旋操作。
左旋:(图片来自教你透彻了解红黑树)
其原理是:以pivot节点为轴向左下“拖拽”(类似于拉绳子,以pivot为固定点,在节点a处发力向左下拉),此时节点Y将成为pivot的父节点,pivot成为Y的左孩子,Y原来的左子树成为pivot的右孩子,同时设置Y为pivot原来父节点P的左孩子,P作为Y的父节点,其伪代码如下:
1 P.left = Y; //设置Y为P的新的左节点 2 Y.parent = P; //设置Y的父节点为P 3 pivot.parent = Y; //将Y设置为pivot的父节点 4 pivot.right = Y.left; //pivot的右节点设为Y的做节点 5 Y.left = pivot; //将pivot设置为Y的左左点
右旋的操作与左旋类似,图中左边的树可以看做是右边树以Y为轴进行右旋得到的结果。
3.插入
红黑树的插入是将待插入节点作为叶子节点(注意新插入的节点不是NIL节点)插入到树中,然后进行相应的调整。这也很好理解,因为BST插入插入操作需要从根节点开始不断与待插入节点进行比较,节点key比待插入节点大,则向左子树走,否则向右子树走,最终会走到叶子节点。很显然,如果树为空,直接将新插入的节点置为黑色即可,如果树不为空,那么插入的节点就不会影响根节点,不会破坏性质1-3,但是可能会破坏性质4和5,为了减少对树进行大的调整的次数,一般会将新插入的节点置为红色,这样就只可能破坏性质4,此时,插入又分为以下情况来(设新插入的节点为x):
①x的父节点为黑色,此时不破坏红黑树的任何性质,不用调整。
②x的父节点p是红色,违反性质4。由于插入节点之前二叉树就是平衡树,那么p的父节点pp(若有)必为黑色,此时的调整过程依赖于x的叔父节点(p的兄弟节点)pb的颜色:
1)若pb为红色,可以将p置为黑色,x的祖父节点pp置为红色,pb置为黑色即可(由于pp变成红色了,可能会影响pp附近的节点,需要从pp开始重新调整)。
2)若pb为黑色,根据x节点是p的左孩子还是右孩子来进行下一步操作。
a)如果x为p的左孩子,将p置为黑色,pp置为红色,此时p的兄弟节点pb那条路径由于pp变红而少了一个黑节点,需要以pp为轴右旋即可;
b)如果x为p的右孩子,将p位置黑色,pp置为红色,同时需要进行两次旋转操作:先以p为轴进行左旋操作,这时就变成了情况a,再以pp为轴右旋即可。
插入的各种情况见下图(图片来源:史上最清晰的红黑树讲解(上))
4.删除
在进行红黑树的删除介绍之前,需要先明确两个概念:前驱和后继。在BST的中序序列中,紧挨着x节点左边的节点称为x的前驱,紧挨着x节点右边的节点称为x的后继。
红黑树的删除,有两种情况:
a)被删除节点z没有孩子(左右子树为空),或者只有一个孩子(有左子树或右子树)。没有孩子直接删除z,有一个孩子的话,用孩子来替换z。这种情况下,如果删除的是红色节点并不影响红黑树的性质,不需要调整;如果删除了黑色节点,那么该条路径的黑色节点数量减少了1,会违反性质5,后续需要进行调整。
b)被删除的节点同时拥有左右孩子,这时就需要用到前驱或后继节点。具体的做法是用被删除节点z的前驱或者后继n替换z,然后删除原来的n的过程。由于z具有左右孩子,那么其前驱必然是左子树中最右边的节点(即左子树中最大的节点,该节点无右孩子),后继必然是右子树中最左的点(即右子树中最小的点,该节点无左孩子)。那么使用前驱或后继替换z之后,问题都会回归到情况a,即删除一个至多只有一个孩子的节点的情况。从目前的资料来看,使用前驱和后继作为替换节点的都有,下面的讲解以后继为主,使用前驱的方式与此类似。
a)被删除的节点z是红色,此时不会违反红黑树的性质,不用进一步调整
b)z是黑色,n是红色,此时经过n的路径少了一个黑色节点,将n变成黑色即可
c)z和n都是黑色,该路径缺少一个黑色节点,那么又有如下两种情况:
①n的兄弟节点nb是红色,那么由红黑树的性质可知,nb的两个孩子节点必然都是黑色,此时将nb置为黑色,n的父节点np置为红色,并以np为轴左旋。
②接情况①,此时n的父节点np变为红色,n的兄弟节点nb是黑色,nb的右孩子nbr是红色,nb的左孩子nbl是黑色,先进行着色,后旋转:nbr置为黑色,nb置为红色,np置为黑色,并以p为轴左旋
③接情况①,np是红色,nb是黑色,nb的右孩子nbr是黑色,左孩子nbl是红色,此时需要进行着色和旋转:将nbl置为黑色,nb置为红色,以nb为轴右旋,此时就转换成了情况②。
还有几个情况没有单独列出来,详细信息见下图(图片来源:史上最清晰的红黑树讲解(下)):
由于个人水平有限,理解难免有误,欢迎指正^_^
【参考】