(一) 2-3 树
2-3树: 是最简单的B-树(或-树)结构, 其每个非叶节点都有两个或三个子节点, 而且一个节点可以包含2个元素.
- 2-3树虽然不是二叉树, 但满足二分搜索树的基本性质.
- 2-3树是一棵绝对平衡的树: 对于任意一个节点, 左右子树的高度是相等的
2-3树的节点可以分为两种:
- 2-节点: 和不同二分搜索树节点一样, 存储一个元素, 有左右两个孩子且左孩子节点元素小于该节点, 右孩子节点元素大于该节点.
- 3-节点: 存储二个元素, 且有左中右三个子孩子. 左孩子节点元素小于该节点存储的第一个元素, 中孩子节点元素在该节点存储的二个元素之间, 右孩子节点元素大于该节点存储的的第二个元素.
2-3树的添加操作
添加操作一定会维持绝对平衡 且 添加的元素不会像二分搜索树添加到NULL节点的位置, 它一定是添加到匹配的叶子节点进行融合
图解: 向空的2-3添加元素 42,37,12,18,6,11,5
(二) 红黑树(RedBlackTree)
红黑树和2-3树是等价的, 可以通过红黑树来描述2-3树: 红色节点代表当前节点和其父节点是2-3树中的3-节点且所有的红色节点都是向左倾斜
1.红黑树是满足以下红黑属性的二叉树:
结合上图理解
- 每个节点要么是红色,要么是黑色
- 根节点是黑色: 在2-3树中根节点要么是2-节点, 要么是3-节点
- 每个叶子节点(最后的空节点)是黑色
- 如果一个节点是红色的,那么他的孩子节点都是黑色的
- 从任意一个节点到叶子节点,经过的黑色节点是一样的
2-3树是一颗绝对平衡的树, 任意节点到叶子节点所进过的节点数是一样的. 2-3树转换为红黑树时, 将3-节点转换为一红一黑的两个节点, 并且红色节点都是向左倾斜, 所以在经历3-节点时一定会先经历黑色节点. 红黑树是保持 “黑平衡” 的二叉树. 严格意义上, 红黑树不是平衡二叉树, 树最大高度为 2logn(每个黑色节点都有一个红色节点)
2.左倾红黑树基础结构
public class RedBlackTree<K extends Comparable<K>, V> {
// 定义常量 RED, BLACK
private static final boolean RED = true;
private static final boolean BLACK = false;
private class Node {
public K key;
public V value;
public Node left;
public Node right;
/**
* 节点的颜色
*/
public boolean color;
public Node(K key, V value) {
this.key = key;
this.value = value;
this.left = null;
this.right = null;
// 默认初始化颜色为红色: 在2-3树中添加元素时一定是先与叶子节点融合, 而RED代表当前节点和其父节点是2-3树中的3节点
color = RED;
}
}
private Node root;
private int size;
public RedBlackTree() {
this.root = null;
this.size = 0;
}
public int getSize() {
return this.size;
}
public boolean isEmpty() {
return this.size == 0;
}
private Node getNode(Node node, K key) {
if (node == null) {
return null;
}
if (node.key.compareTo(key) == 0) {
return node;
} else if (node.key.compareTo(key) < 0) {
return getNode(node.right, key);
} else {
return getNode(node.left, key);
}
}
/**
* 判断节点node的颜色, node为null返回BALCK (RED代表当前节点和其父节点是2-3树中的3节点)
*
* @param node
* @return
*/
private boolean isRed(Node node) {
if (node == null) {
return BLACK;
}
return node.color;
}
}
3.红黑树添加操作
2-3树中添加一个新元素, 要么添加进2-节点, 形成一个3-节点, 要么添加精3- 节点, 暂时形成一个4- 节点. 所以红黑树添加的新节点一定是红色的.
- 红黑树添加操作的第一种情况: 添加的新元素(红色)在根节点(黑色)的右侧, 左旋转 node节点 与 x节点融合成 3-节点
private Node leftRotate(Node node) {
Node x = node.right;
// 左旋转
node.right = x.left;
x.left = node;
// 维护节点颜色, 有可能得到的x节点和node节点都为红色
x.color = node.color;
// node节点一定为红色, node节点 + x节点 组成 3-节点
node.color = RED;
return x;
}
- 红黑树添加操作的第二种情况: 添加的新元素(红色)在3-节点的右侧, 颜色翻转flipColors
private void flipColors(Node node) {
node.color = RED;
node.left.color = BLACK;
node.right.color = BLACK;
}
- 红黑树添加操作的第三种情况: 添加的新元素(红色)在3-节点的左侧, 右旋转 node节点 与 x节点融合成 3-节点
private Node rightRotate(Node node) {
Node x = node.left;
// 右旋转
node.left = x.right;
x.right = node;
// 维护节点颜色
x.color = node.color;
// node节点一定为红色, node节点 + x节点 组成 3-节点
node.color = RED;
return x;
}
- 红黑树添加操作的第四, 五种情况
public void add(K key, V value) {
this.root = add(this.root, key, value);
// 保持根节点是黑色节点
root.color = BLACK;
}
/**
* 向以node为根的红黑树中插入键值对key-value, 返回插入新节点后红黑树的跟
*
* @param node
* @param key
* @param value
* @return
*/
private Node add(Node node, K key, V value) {
if (node == null) {
size++;
return new Node(key, value);
}
// 二分搜索树中存在插入key, 修改key对应的value
if (node.key.compareTo(key) == 0) {
node.value = value;
} else if (node.key.compareTo(key) < 0) {
node.right = add(node.right, key, value);
} else if (node.key.compareTo(key) > 0) {
node.left = add(node.left, key, value);
}
// 当前结点的右孩子为红色 且 当前结点的左孩子不为红色
if (isRed(node.right) && !isRed(node.left)) {
// 左旋转
node = leftRotate(node);
}
// 当前结点的右孩子为红色 且 当前结点的右孩子的右孩子为红色
if (isRed(node.left) && isRed(node.left.left)) {
// 右旋转
node = rightRotate(node);
}
// 当前结点的左右孩子都为红色
if (isRed(node.right) && isRed(node.left)) {
// 颜色翻转
flipColors(node);
}
return node;
}
java.util中的TreeMap和TreeSet基于红黑树