文章目录
- 1 前言
- 2 定义
- 2.1 替换3-结点
- 2.2 等价定义
- 2.3 一一对应
- 3 基础
- 3.1 颜色表示
- 3.2 结点表示
- 3.2 旋转
- 3.3 旋转后重置父结点的链接
- 后记
1 前言
目前我学习到的2种红黑树:一种是红黑树的标准定义,另外一种是《算法第4版》中的红黑树。后一种定义只比前一种多了一个条件:红链接均为左链接(红色结点均为左子结点)。
关于对红黑树的理解,可以把它当做前面我们学习的2-3树的一种特殊形式,按照2-3树的思想来学习理解。那如果之前没有接触过2-3树呢?拿就把它当做一种新的二叉树结构来学习就好,没必要纠结这个。只要最终我们实现它的基本操作,且操作完成后它依然符合红黑树的性质(定义)就好。
我们先来讲解第二种定义的红黑树,按2-3树来理解,主要参考《算法第4版》;后面会以一种新的二叉树的视角来讲解第一种红黑树,主要参考JDK8+ 的HashMap中的红黑树。最后,我们会对这两种方式做对比。
2 定义
2.1 替换3-结点
红黑二叉查找树基本思想是用标准的二叉查找树(完全由2-结点构成)和一些额外的信息(替换3-结点)来表示2-3树。我们把树中的链接分为2中类型:红链接将两个2-结点连接起来构成一个3-结点,黑链接则是2-3树中的普通链接。确切的说我们将3-结点表示为一条左斜的红色链接(两个2-结点其中之一是另外一个的左子结点)相连的两个2-结点。
2.2 等价定义
红黑树的另外一种定义是含义红黑树链接并满足下列条件的二叉查找树:
- 红链接均为左链接
- 没有任何一个结点同时和两条红色链接相连;
- 该树是完美黑色平衡的,即任意空链接到根结点的路径上的黑链接数量相同。
满足这样定义的红黑树和相应的2-3树是一一对应的。
2.3 一一对应
如果将一棵红黑树中的红链接画平,那么所有的空链接到根结点的距离都将是相同的。如果我们将由红链接相连的的结点合并,得到的就是一棵2-3树。相反,如果一棵2-3树中的3-结点画作由红链接链接的2-结点,那么不会存在能够和两条红链接链接的结点,且树必然是完美黑色平衡的,因为黑链接即2-3树中的普通链接,根据定义这些链接必然是完美平衡的,如下图所示:
因此,如果我们能够在保持一一对应关系的基础上实现2-3树的插入算法,那么我们就能够将两个算法的有的结合起来:二叉查找树中简洁高效的查找方法和2-3树中高效的平衡插入算法。
3 基础
3.1 颜色表示
方便起见,因为每个结点都只会有一条指向自己的链接(从它的父结点指向它),我们将结点的颜色保存在表示结点的Node数据类型的布尔变量color中。如果指向它的链接是红色的,那么该变量为true,黑色则表示false。我们约定空链接为黑色。为了代码的清晰,定义了2个常量RED和BLACK来设置和测试这个变量。我们使用私有方法isRed()来测试一个结点和它的父结点之间链接的颜色。isRed源代码如下:
/*** 链接(结点)颜色 RED = true表示红链接;BLACK = false表示黑链接*/
private static final boolean RED = true;
private static final boolean BLACK = false;
/*** 判断当前结点和父链接是否为红色* @param x 当前结点* @return 当前结点不为空且当前结点color属性为RED,返回{@code true};否则返回{@code false}*/
private boolean isRed(Node x) { return x != null && x.color == RED;}
3.2 结点表示
/*** 结点类*/
class Node {/*** 键*/K key;/*** 值*/V value;/*** 左右链接(子结点)*/Node left, right;/*** 子树结点个数*/int n;/*** 链接颜色*/boolean color;public Node(K key, V value, Node left, Node right) {this.key = key;this.value = value;this.left = left;this.right = right;// 默认新建结点为根结点的子树结点个数为1this.n = 1;// 默认新建结点颜色为红色this.color = RED;}public Node(K key, V value) {this(key, value, null, null);}
}
3.2 旋转
在我们实现的某些操作中可能会出现红色链接或者两条连续的红链接,但在操作完成之前这些情况都会被小心地旋转并修复。选择操作会改变红链接的指向。把一条红色的右链接转化为左链接,称为左旋[转]。如下图所示:
非递归左旋代码3.2-1实现如下:
/*** 以子树根结点为轴左旋* @param p 子树根结点* @return 左旋完成后的子树根结点*/
private Node rotateLeft(Node p) {// 调整链接Node x = p.right;int n = 1 + (x.right == null ? 0: x.right.n);p.right = x.left;x.left = p;// 调整颜色x.color = p.color;p.color = RED;// 计算结点数x.n = p.n;p.n = x.n - n;return x;
}
把一条红色左链接转化为右链接,称为右旋[转],图示如下:
非递归代码实现代码3.2-2如下:
/*** 以子树根结点为轴右旋* @param p 子树根结点* @return 右旋完成后的子树根结点*/
private Node rotateRight(Node p) {// 调整链接Node x = p.left;int n = 1 + (x.left == null ? 0: x.left.n);p.left = x.right;x.right = p;// 调整颜色x.color = p.color;p.color = RED;// 计算结点数x.n = p.n;p.n = x.n - n;return x;
}
3.3 旋转后重置父结点的链接
无论是左旋还是右旋,操作完成后返回一条链接,我们会把它赋予父结点中链接,可能是红色也可能是黑色。这可能产生两条连续的红链接,我们会通过旋转操作修正。
后续讲解插入、删除、有序性想关操作等。
后记
如果小伙伴什么问题或者指教,欢迎交流。
❓QQ:806797785
⭐️源代码仓库地址:https://gitee.com/gaogzhen/algorithm
[1][美]Robert Sedgewich,[美]Kevin Wayne著;谢路云译.算法:第4版[M].北京:人民邮电出版社,2012.10