二叉树实战(九):红黑树
我们知道2-3树能保证在插入元素后,树依然保持平衡状态,它最坏的情况就是所有的子节点都是2-节点,这样树的高度为logN,但是和普通的二叉查找树相比,最坏的情况下树的高度为N,确实能保证最坏情况下的时间复杂度,但是2-3树实现起来过于复杂,所以我们这里介绍一种基于2-3树实现的树结构 — 红黑树。
一、红黑树的定义
红黑树主要是对2-3树进行编码,红黑树的基本思想就是用标准的二叉查找树(完全有2-结点组成)和一些额外的信息(替换3-结点)来表示2-3树。
红黑树将树中的链接分为两种类型:
- 红链接:将两个2-结点链接起来构成一个3-结点;
- 黑链接:2-3树中的普通链接。
确切的说,红黑树就是将3-结点表示为由一条左斜的红色链接(两个2-结点,一个是另一个的左子节点)相连的两个2-结点。具体的结构图如下:

这样表示2-3树的优点就是,我们无需修改,就可以使用标准二叉查找树的get方法。
这里再说明一下红黑树的特点:
- 红链接皆为左链接;
- 没有任何一个结点能同时和两条红链接相连(如果相连了就是4-结点了);
- 该树是完美黑色平衡的,即在任意空链接到根连接的黑链数量是相同的。
这里我们把上图的红色链接进行水平绘制,可以看出来,就是一个标准的2-3树了。

这也说明红黑树就是基于2-3树进行实现的。
二、红黑树的结点定义
因为每个结点都会只有一条指向自己的链接(从父节点指向自己),我们可以在Node结点中添加一个Boolean类型的color来表示链接的颜色。如果指向它的链接是红色的,那么该变量的值为true,如果链接是黑色的,那么该变量的值为false.
结点定义的示意图如下:

代码实现:
private class Node<Key, Value> { public Key key; // 存储结点的键 public Value value; // 存储结点的值 public Node left; // 记录左子节点 public Node right; // 记录右子节点 public boolean color; // 记录其父结点指向它的链接的颜色 public Node(Key key, Value value, Node left, Node right, boolean color) { this.key = key; this.value = value; this.left = left; this.right = right; this.color = color; } }
三、红黑树的平衡化
在上面我讲了红黑树的特点:
- 红链接皆为左链接;
- 没有任何一个结点能同时和两条红链接相连(如果相连了就是4-结点了);
- 该树是完美黑色平衡的,即在任意空链接到根连接的黑链数量是相同的。
当我们在对红黑树进行了一些增删操作之后,很有可能出现红色的右链接或者两条连续红色的链接,但是出现这些情况会使得树不满足我们对红黑树的定义,所以我们需要通过旋转操作来进行修复,让红黑树继续保持平衡。
1、左旋
当某个结点的左子结点为黑色,右子结点为红色的时候,需要进行左旋,具体示意图如下:

这里的E值对应的结点为h,它的右子结点为x。左旋过程如下:
- 让x的左子节点变为h的右子结点:h.right = x.left
- 让h称为x的左子结点:x.left = h
- 让x的color属性变为h的color属性值:x.color = h.color
- 让h的color属性变为RED:h.color = true
2、右旋
当某个结点的左子结点是红色,且左子结点的左子结点也是红色的时候,需要进行右旋。具体示意图如下:

这里当前结点为h,它的左子结点为x。右旋过程如下:
- 让x的右子结点作为h的左子结点:h.left = x.right
- 让h称为x的右子结点:x.right = h
- 让x的color变为h的color:x.color = h.color
- 让h的color为RED
3、颜色反转
当一个结点的左子结点和右子结点的color都为RED的时候,也就是出现了临时的4-结点,此时只需要把左子结点和右子结点的颜色变为Black,同时让当前结点的颜色变为Red即可。

这里需要注意的是,结点的color属性指的是父结点指向当前结点的连接的颜色额,由于根结点不存在父结点,所以每次插入操作后,我们都需要把根节点的颜色设置为黑色。
四、红黑树的代码实现
/** * 红黑树的实现 */ public class RedBlackTree<Key extends Comparable<Key>, Value> { public static void main(String[] args) { //创建红黑树 RedBlackTree<String, String> tree = new RedBlackTree<>(); //往树中插入元素 tree.put("1","张三"); tree.put("2","李四"); tree.put("3","王五"); //从树中获取元素 String r1 = tree.get("1"); System.out.println(r1); String r2 = tree.get("2"); System.out.println(r2); String r3 = tree.get("3"); System.out.println(r3); } private static final boolean RED = true; // 红色链接标识 private static final boolean BLACK = false; // 黑色链接标识 private Node root; // 记录根结点 private int N; // 记录树中元素的个数 // 左旋转 private Node rotateLeft(Node h) { // 获取h节点的右子结点,标为x Node x = h.right; // 让x结点的左子结点为h的右子结点 h.right = x.left; // 让h称为x结点的左子结点 x.left = h; // 让x的color属性 = h的color属性 x.color = h.color; // 让h的color = RED h.color = RED; return x; } // 右旋转 private Node rotateRight(Node h) { // 获取h结点的左子结点,标为x Node x = h.left; // 让x节点的右子结点称为h结点的左子结点 h.left = x.right; // 让h结点称为x结点的右子结点 x.right = h; // 让x的color = h的color x.color = h.color; // 让h的color = RED h.color = RED; return x; } // 颜色反转,相当于拆分4-结点 private void flipColors(Node h) { // 当前结点变为红色 h.color = RED; // 左子结点和右子结点变为黑色 h.left.color = BLACK; h.right.color = BLACK; } // API - 插入操作 public void put(Key key, Value value) { root = put(root, key, value); // 保证根结点的颜色总为黑色 root.color = BLACK; } // 内部 - 向指定树插入元素,并返回添加元素后的新树 private Node put(Node h, Key key, Value value) { // 判断h是否为空,如果为空,则直接返回一个红色的结点 if (h == null) { N++; return new Node(key, value, null, null, RED); } // 比较h的键和k的大小 int cmp = key.compareTo(h.key); if (cmp < 0) { // 继续往左 h.left = put(h.left, key, value); } else if (cmp > 0) { // 继续往右 h.right = put(h.right, key, value); } else if (cmp == 0) { // 替换 h.value = value; } // 进行左旋 if(isRed(h.right) && !isRed(h.left)) { h = rotateLeft(h); } // 进行右旋 if (isRed(h.left) && isRed(h.left.left)) { h = rotateRight(h); } // 进行颜色反转 if (isRed(h.left) && isRed(h.right)) { flipColors(h); } return h; } // 判断当前结点的父节点指向链接是否为红色 private boolean isRed(Node x) { if (x == null) { return false; } return x.color == RED; } // 获取树中元素的个数 private int getSize() { return N; } //根据key,从树中找出对应的值 public Value get(Key key) { return get(root,key); } //从指定的树x中,查找key对应的值 public Value get(Node x, Key key) { if (x == null){ return null; } //比较x结点的键和key的大小 int cmp = key.compareTo(x.key); if (cmp<0){ return get(x.left,key); }else if (cmp>0){ return get(x.right,key); }else{ return x.value; } } private class Node { public Key key; // 存储结点的键 public Value value; // 存储结点的值 public Node left; // 记录左子节点 public Node right; // 记录右子节点 public boolean color; // 记录其父结点指向它的链接的颜色 public Node(Key key, Value value, Node left, Node right, boolean color) { this.key = key; this.value = value; this.left = left; this.right = right; this.color = color; } } }
输出结果:
张三
李四
王五

浙公网安备 33010602011771号