由于面试被问到红黑树,特意查阅资料,并做此笔记。
1、前言
谈到红黑树,首先需要了解二叉搜索树,其特点主要有:
1、有序的,或排序的,二叉树;
2、节点可以有2个子树;
3、左子树的值比较小;
4、右子树的值比较大
下面是一个二叉搜索树的例子:
当一个搜索二叉树变成下面这样,有会怎样呢
可想而知,如果想搜索值为1 的节点,需要遍历所有的节点。他的时间复杂度也就是O(n)。解决这个问题就需要用到平衡搜索树,他的时间复杂度是O(log n),红黑树是特殊的一种平衡二叉树,其性质有:
1、其节点是红色或黑色。
2、根节点和叶子节点都是黑色(叶子是NIL节点)。
3、每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
4、从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
额外注释:
1、节点需要一个存储位来跟踪颜色。
2、最长路径(从根到最远的零)不超过最短路径长度的两倍(从根到最近的零)。
-最短路径:所有黑色节点
-最长路径:红黑相间
下面是一个红黑树方便理解:
2、红黑树的操作
操作及时间复杂度
1、查询 O(log n)
2、插入 O(log n)
3、删除 O(log n)
空间复杂度为O(n)
其中插入和删除一个节点有可能改变树的结构,可能会违背了红黑树的性质,为了防止出现这一问题,我们需要用到旋转。
2.1 旋转
1、通过重新排列子树来改变树的结构
2、目标是降低树的高度。
- 红黑树:最大高度 O(log n)
- 较大的子树向上,较小的子树向下
3、不影响元素的顺序(较小的节点仍在左边,较大的节点仍在右边)
先不把树分为黑白两色,这样容易理解如何旋转
1、左旋:
向左旋转值为 5 的节点:
5 变成左孩子节点;10 变成父节点;8 变成 5 的右孩子节点;依次画出新的红黑树。下图为旋转之后的样子:
2、右旋:
向右旋转值为 10 的节点:
10 的左孩子 5 变成其父节点;10 变成 5 的右孩子节点;8 变成 10 的左孩子节点;依次画出新的红黑树,如下图:
可以发现:旋转之后的红黑树依旧保持其属性,值较小的节点在左边,值较大的节点在右边。
2.2 插入
前面我们已经发现,红黑树是一种自平横的二叉树,当我们插入时也必须保证新树保持平衡。
步骤:
1、插入 Z 节点并将其涂成红色
2、重新着色和旋转节点以修复冲突(维持红黑树的性质)
*对于第二步,又分为四种情况:
case 1:Z 为根节点
case 2:Z 的叔叔节点为红色
case 3:Z 的叔叔节点为黑色(triangle)
case 4:Z 的叔叔节点为黑色(line)
case 1:Z 为根节点
只有一个点,直接将节点颜色变为黑色。如下图:
case 2:Z 的叔叔节点为红色
当 Z 父节点的兄弟节点为红色这种情况下,需要改变 Z 节点的父节点、叔叔节点以及 Z 节点的祖父节点的颜色,如下图:
case 3:Z 的叔叔节点为黑色(triangle)
如下图,插入 Z 时,Z 节点的叔叔节点为黑色,且 Z节点与 Z 的父节点及Z 的祖父节点的连线为三角形:
在这种情况下,需要旋转A节点和Z节点,如下图:
case 4:Z 的叔叔节点为黑色(line)
如下图,插入 Z 时,Z 节点的叔叔节点为黑色,且 Z节点与 Z 的父节点及Z 的祖父节点的连线为直线型:
这种情况下,首先需要旋转B和A,用A替换掉B作为新的根节点,B变成A的左子树,依次建立新的红黑树,如下图:
然后改变颜色,如下图:
总结:
1、Z 为根节点 => 直接变为黑色
2、Z 的叔叔节点为红色 => 改变相应节点的颜色
3、Z 的叔叔节点为黑色(三角型) => 旋转Z与Z的父节点
4、Z 的叔叔节点为黑色(l直线型) => 旋转Z的祖父节点与Z的父节点;并改变相应节点的颜色。
例子
如上图,有一棵红黑树(为了方便,叶子结点没有画出),现将要插入值为10的节点,具体操作如下所述:
1、10节点需要插在9的右子树上,这时需要旋转红黑树,Z为值为10的节点,uncle为值为13的节点,这时即为上述case 2,改变Z父节点、叔叔节点及祖父节点的颜色,如下图:
2、继而发现,12与15节点的颜色同时为红色,违背了红黑树的性质,则将12看做新插入的Z节点,继续修改,容易发现现在便是case 3 [Z 的叔叔节点为黑色(三角型)],根据上述改变规则,将12旋转为15的父节点,15旋转为12的右子树根节点,依次建立新的红黑树,如下图:
此时,可以发现,12与15两个节点同时为红色,即为case 4 [Z 的叔叔节点为黑色(line)],根据上述改变规则,将12旋转为8的父节点,8旋转为12 的左子树根节点,并依次建立新的红黑树,如下图:
最后重新着色:
完毕。