作为引入,首先讲一下2-3-4树:
在普通的二叉查找树上进行了扩展,它允许有多个键(1~3个)
树保持完美平衡
完美平衡?根到每个叶子结点的路径都是一样长。
特点:动态地添加和删除元素时,能保持树的完美平衡
每个结点可以拥有1, 2, 或者3个键。
2-node:1个键,2个孩子
3-node:2个键,3个孩子
4-node:3个键,4个孩子
···································································································································································································
查找
和当前结点的所有的键进行比较
如果当前结点中没有,就找到对应的区间
依据链接找到下一个结点 (递归)
···································································································································································································
插入1
查找键应该插入的位置 (树底)
查找键应该插入的位置 (树底)
2-node:转换成3-node
插入2
查找键应该插入的位置 (树底)
2-node:转换成3-node
3-node:转换成4-node
插入3
查找键应该插入的位置 (树底)
2-node:转换成3-node
3-node:转换成4-node
4-node:咋办呢?
2-3-4树 —— 4结点分裂
我们需要分裂4结点,为新插入的结点腾出空间。
小问题:如果父节点也是4-node,又该怎么办呢?
两种解决方案:
Bottom-up 自底向上(找到插入位置后,然后会逐步向上进行4结点分裂,最多会遍历两遍)
Top-down 自顶向下(一般用这种,只遍历一遍就可以了)
···································································································································································································
Top-down方法:
确保当前结点不是 4-node,预留空间给新元素。
沿着查找路径向下分裂4-node
在底部插入元素
Case 1: 根结点是 4-node(这步过后树的高度会+1)
Case 2: 父节点是2-node
Case 3: 父节点是3-node
不变式:当前结点不是4-node
结果:
1. 4-node 的父亲不是 4-node。
2. 到达的叶子结点要么是2-node,要么是3-node。
Note:这些变换都是局部变换,不会影响到无关的其他结点。
局部变换只会影响局部的一些结点。如下图:
···································································································································································································
举个例子:
···································································································································································································
2-3-4树——性能分析
主要性质:2-3-4树是一棵完美平衡的树。
树的高度
最坏情况: lg N [全部是2-node]
最好情况: log4N = ½ lg N [全部是4-node]
100万个结点高度在[10, 20]
10亿个结点高度在[15, 30]
2-3-4树保证了树的高度为 O(lgN) !
····································································································································································································
怎么实现呢?直接实现?
为2-node, 3-node, 4-node 编写不同的结点类
不同的结点类之间需要相互转换
不好统一不同的case
打致思路就是如下:
可以实现,但是代码复杂度太高,我们期待更简单的实现!
怎么更简单的实现?红黑树!
····································································································································································································
Red-black trees
用 BST 来表现 2-3-4 树
用”内部的”红色边来表示 3-node 和 4-node
算法导论中关于红黑树的定义:
一棵红黑树是满足下面红黑性质的二叉搜索树:
- 每个结点或者是红色的,或者是黑色的(理解为:一个结点和父节点的之间的边,是红色的或是黑色的)
- 根结点是黑色的
- 叶结点 (Nil) 是黑色的
- 如果一个结点是红色的,则它的两个子结点都是黑色的 (4-node 只有一种编码方式)
- 对每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点。(黑高平衡, 即:2-3-4树是一个完美平衡的树)
2-3-4 树能够被表示成 BST,它们之间有一种对应关系!但是它们这种对应不是1-1的。
如果有n个3结点,就可以表示成4*n个不同的红黑树。
····································································································································································································
所以就有了左倾红黑树:
Left-leaning red-black trees
用 BST 来表现 2-3-4 树
用”内部的”红色边来表示 3-node 和 4-node
3-node 的红色边是左倾的
2-3-4 树能够被表示成 BST,它们之间有一种对应关系!它们这种对应是1-1的!
我们在普通的BST结点类中添加表示颜色的属性 color。
LLRB—— 查找
查找
和基本的BST查找代码一模一样。
在讲插入之前,我们来看下基本的树的局部变换——旋转。
左旋:
右旋:
在LLRB中,我们需要通过左旋和右旋操作来保证LLRB的性质!
a.满足查找树的性质
b.保证黑高平衡
LLRB —— 插入
回顾下 2-3-4 树的插入
沿着查找路径,自顶向下分解 4-node,确保当前结点不是 4-node
在底部插入新结点
Note: 分解 4-node 的操作都是局部变换。
LLRB 与 2-3-4 树是 1-1 对应的,所以它插入的思路也是一样的。
我们先来考察在底部插入新结点的情形。
父节点要么是 2-node, 要么是 3-node,根据插入位置的不同,总共有 2 + 3 = 5 种情况。
插入新结点,会造成右倾的3-node,和连续的红链接 (不规范的4-node)。我们需要通过旋转操作来更正这些链接。
分裂 4-node,可以通过一个简单的颜色反转来实现。
Note:
这也是一个局部变换
保持了黑高的平衡
将红色链接传递给了父结点
相当于在父结点中插入一个新的结点
在父结点中插入新结点的情形和在底部插入新结点的情形是一模一样的
····································································································································································································
我们再撸一撸LLRB的插入过程:
- 自顶向下,沿查找路径分解4-node
- 在底端添加新结点
- 自底向上,通过旋转操作来更正非法的 3-node 和 4-node (分解4-node 和添加新结点都可能产生)。
更正非法的 3-node 和 4-node 有三种情况,我们统一成两个步骤。
- 左旋任意的红色右链接。
- 如果有两条连续的左倾红色链接,右旋上面的红色链接
大致代码:
.
思考:如果我们将反转操作放到最后,情况又是怎样呢?
这样我们得到的将会是一颗2-3树(没有4结点),而不是2-3-4树。
····································································································································································································
LLRB —— 删除最大值
在讲一般的删除之前,我们讲讲删除的一种简单情况,删除最大值。
思路:
沿着最右边的分支向下查找
如果最大结点是3-node, 或者4-node——直接删除 (不影响2-3-4树的完美平衡)
如果最大结点是2-node —— 咋办呢? 删除一个2结点则会影响2-3-4树的完美平衡
回想一下插入算法是怎么做的。
插入算法为了保证不在4-node中插入新结点,会自顶向下分解4-node,确保当前结点不是4-node, 为新结点预留空间。
同样的,删除最大值算法思路:确保当前结点不是2-node。
第一种情况:当前结点不是根节点
- 当父节点是3结点时:比如在父节点的最右侧的孩子结点处插入元素,以下三图分别是删除结点的兄弟结点为2、3、4结点的情况。
- 删除结点的兄弟结点为2结点:兄弟结点靠过来,父节点的最右侧结点下来,将删除结点靠成一个四结点。
- 删除结点的兄弟结点为3结点:父节点的最右侧结点下来,兄弟结点的最右侧结点靠到父节点的最右侧结点处。此时删除结点是一个3节点。
- 删除结点的兄弟结点为4结点:操作同3结点的情况。
- 当父节点是4结点时:比如在父节点的最右侧的孩子结点处插入元素,以下三图分别是删除结点的兄弟结点为2、3、4结点的情况。
其三种情况的处理过程同上
我们再来看看根结点的情况:
根节点不是2-node。与前面分析的一样。
根结点是2-node。
对于 2-3 树而言,删除最大值的策略。
不变式: 保证当前结点不是2-node。
必要的时候,我们可以引入4-node
在最底端删除最大值
沿查找路径自底向上fixUp()。
实现:
遇到左倾的红色链接,右旋
不变式:当前结点不是2-node
在最底端删除
-
右旋红色链接
-
如果右孩子是2-node, 我们需要从它的兄弟结借结点。这有两种情况。
右孩子是 2-node:h.right AND h.right.left both BLACK
Case 1: 左孩子是 2-node (h.left.left is BLACK)
(我们是自顶向下调整结点的,之前建立的是左倾的2-3树,所以判断左孩子是2-node:h.left.left is BLACK)
Case 2: 左孩子是 3-node (h.left.left is RED)
完成这些步骤之后,我们就可以在底端删除最大结点了。但是,我们发现这些局部变换会引入红色的右链接和4-node。我们需要自底向上修复这些不规范的结点。
deleteMax() 完整代码
举个栗子,一目鸟然:
····································································································································································································
LLRB —— 删除最小值
删除最小值的策略和删除最大值的策略是一致的,只是有些许不同。
不变式: 保证当前结点不是2-node。
必要的时候,我们可以引入4-node
在最底端删除最小值
沿查找路径自底向上fixUp()。
实现:
不变式:当前结点不是2-node
在最底端删除
我们讨论的是 2-3 树模型,因此是没有红色的右链接的,故不需要左旋。
如果左孩子是2-node (!h.left.isRed() && !h.left.left.isRed()),
我们需要从兄弟中借结点,这分两种情况。
Case 1: 右孩子是 2-node (h.right.left is BLACK)
Case 2: 右孩子是 3-node (h.right.left is RED)
完成这些步骤之后,我们就可以在底端删除最小结点了。但是,我们发现这些局部变换会产生右链接和引入4-node。我们需要自底向上修复这些不规范的结点。可以复用fixUp()。
deleteMin() 完整代码
举个栗子:
····································································································································································································
LLRB —— 删除一般结点
我们先试试用删除最大最小值的思路去删除任意结点。
查找要删除的结点
如果要删除的结点是3-node或者是4-node,那么直接删除不会影响黑高平衡?
如果是2-node
自顶向下变换树
保证当前结点不是2-node
问题:如果待删不是叶子结点,是不能直接删除的!
有太多种情况需要考虑!
像BST一样,其实我们可以将问题转换成deleteMin().
····································································································································································································
package com.cskaoyan.rb;
import com.sun.org.apache.regexp.internal.RE;
import sun.reflect.generics.tree.Tree;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* @author shihao
* @create 2020-05-27 8:39
* <p>
* API:
* 无序符号表:
* void put(K key, V value)
* // V get(K key)
* void delete(K key)
* void clear()
* // boolean contains(K key)
* boolean isEmpty()
* int size()
* Set<K> keys()
* 有序符号表:
* K min()
* K max()
* K floor(K key)
* K ceiling(K key)
* int rank(K key)
* K select(int k)
* void deleteMin()
* void deleteMax()
* int size(K low, K high)
* Set<K> keys(K low, K high)
*/
public class RedBlackTree<K extends Comparable<? super K>, V> {
//常量
private static final boolean RED = true;
private static final boolean BLACK = false;
//属性
private TreeNode root;
private class TreeNode {
K key;
V value;
boolean color;
int size; //这棵树包含的结点个数
TreeNode left;
TreeNode right;
public TreeNode(K key, V value, boolean color, int size) {
this.key = key;
this.value = value;
this.color = color;
this.size = size;
}
}
//构造方法
//root的初始值为null,所以使用默认构造方法就可以了
//方法
/***********************************************************
* Unordered table methods
***********************************************************/
/**
* 获取键关联的值
*
* @param key 键
* @return 关联的值
*/
public V get(K key) {
if (key == null) {
throw new IllegalArgumentException("key cannot be null!");
}
TreeNode x = root;
while (x != null) {
int cmp = key.compareTo(x.key);
if (cmp < 0) x = x.left;
else if (cmp > 0) x = x.right;
else return x.value;
}
//假设我们的红黑树中不能存取null键和null值
return null;
}
/**
* 判断红黑树中是否包含指定的键
*
* @param key 键
* @return 如果包含返回true,否则返回false
*/
public boolean contains(K key) {
return get(key) != null;
}
/**
* 插入结点
*
* @param key 键
* @param value 值
*/
public void put(K key, V value) {
if (key == null || value == null) {
throw new IllegalArgumentException("key or value cannot be null");
}
//在root中插入结点
root = put(root, key, value);
//在自底向上修复的过程中可能会把根节点的颜色变成红色
//所以,最后应当将根节点颜色置为黑色
root.color = BLACK;
//每次put后都查看是否满足黑高平衡和是否是2-3树
check();
}
/**
* @param x
* @param key
* @param value
* @return
*/
private TreeNode put(TreeNode x, K key, V value) {
//在底端插入
if (x == null) return new TreeNode(key, value, RED, 1);
//自顶向下分解4-node
//if (isRed(x.left) && isRed(x.right)) filpColor(x);
//查找过程,BST的标准插入代码
int cmp = key.compareTo(x.key);
if (cmp < 0) x.left = put(x.left, key, value);
else if (cmp > 0) x.right = put(x.right, key, value);
else x.value = value;
//自底向上修复
return fixUp(x);
}
/**
* 删除键为key的结点
*
* @param key
*/
public void delete(K key) {
if (key == null) {
throw new IllegalArgumentException("key cannot be null!");
}
if (root == null) {
throw new NoSuchElementException("The tree is Empty!");
}
if (!contains(key)) return;
//key一定存在,需要删除结点
if (!isRed(root.left)) root.color = RED;
root = delete(root, key);
if (root != null) root.color = BLACK;
check();
}
private TreeNode delete(TreeNode x, K key) {
if (key.compareTo(x.key) < 0) {
//如果左孩子是2结点,就需要从右孩子中借结点
if (!isRed(x.left) && !isRed(x.left.left)) {
x = moveRedLeft(x);
}
//往左走
x.left = delete(x.left, key);
} else {
//右旋左倾的红色链接,下一步判断x.right == null为true的话就表示
// x已经是叶子结点,有人说x可能有个左孩子存在,不可能,我已经把x右旋了
if (isRed(x.left)) x = rotateRight(x);
//在底端删除结点
if (key.compareTo(x.key) == 0 && x.right == null) return null;
//如果右孩子是2结点,需要从左孩子借结点过去
if (!isRed(x.right) && !isRed(x.right.left)) {
x = moveRedRight(x);
}
if (key.compareTo(x.key) == 0) {
//找右孩子的最小结点替换当前结点
TreeNode minOfRight = min(x.right);
x.key = minOfRight.key;
x.value = minOfRight.value;
//在右子树中删除最小值结点
x.right = deleteMin(x.right);
} else { //else即:key.compareTo(x.key) > 0
x.right = delete(x.right, key);
}
}
//自底向上进行修复
return fixUp(x);
}
/**
* 获取红黑树中键值对的个数
*
* @return 键值对的个数
*/
public int size() {
return size(root);
}
/**
* 判空
*
* @return
*/
public boolean isEmpty() {
return root == null;
}
/**
* 清空所有键值对
*/
public void clear() {
root = null;
}
/**
* 获取键的集合
*
* @return 键的集合
*/
public Set<K> keys() {
if (isEmpty()) return new LinkedHashSet<>();
return keys(Min(), Max());
}
/***********************************************************
* Ordered table methods
***********************************************************/
/**
* 找最小值结点
*
* @param node
* @return
*/
public TreeNode min(TreeNode node) {
TreeNode x = node;
while (x.left != null) {
x = x.left;
}
return x;
}
/**
* 获取红黑树中最小的键
*
* @return 最小键
*/
public K Min() {
if (isEmpty()) {
throw new NoSuchElementException("The tree is Empty!");
}
return min(root).key;
}
/**
* 找最大值结点
*
* @param node
* @return
*/
public TreeNode max(TreeNode node) {
TreeNode x = node;
while (x.right != null) {
x = x.right;
}
return x;
}
/**
* 获取红黑树中最大的键
*
* @return 最大键
*/
public K Max() {
if (isEmpty()) {
throw new NoSuchElementException("The tree is Empty!");
}
return max(root).key;
}
/**
* 删除最大节点
*/
public void deleteMax() {
if (root == null) {
throw new NoSuchElementException("The tree is Empty!");
}
//确保进入条件,如果根节点是2结点,将根节点染红
if (isRed(root.left)) root.color = RED;
root = deleteMax(root);
//root依然存在的情况下将其染黑,不判断小心空指针异常
if (root != null) root.color = BLACK;
check();
}
private TreeNode deleteMax(TreeNode x) {
//右旋左倾的红色链接
if (isRed(x.left)) x = rotateRight(x);
//如果x是最大的结点,直接删除
if (x.right == null) return null;
//先判断右孩子是不是2结点
if (!isRed(x.right) && !isRed(x.right.left)) {
x = moveRedRight(x);
}
//往右走
x.right = deleteMax(x.right);
//修复结点
return fixUp(x);
}
/**
* 删除最小节点
*/
public void deleteMin() {
if (root == null) {
throw new NoSuchElementException("The tree is Empty!");
}
//确保进入条件,如果根节点是2结点,将根节点染红
if (isRed(root.left)) root.color = RED;
root = deleteMin(root);
//root依然存在的情况下将其染黑,不判断小心空指针异常
if (root != null) root.color = BLACK;
check();
}
private TreeNode deleteMin(TreeNode x) {
//删除最小值结点
if (x.left == null) return null;
//判断左孩子是不是2结点,是的话需要从右孩子中借结点过去
if (!isRed(x.left) && !isRed(x.left.left)) {
x = moveRedLeft(x);
}
//往左走
x.left = deleteMin(x.left);
//自底向上进行修复
return fixUp(x);
}
/**
* 获取小于等于key的最大键
*
* @param key 键
* @return 小于等于key的最大键
*/
public K floor(K key) {
if (key == null) {
throw new IllegalArgumentException("key cannot be null");
}
TreeNode x = floor(root, key);
if (x != null) return x.key;
return null;
}
private TreeNode floor(TreeNode x, K key) {
//边界条件,如果找到叶子结点了还没找到就返回null
if (x == null) return null;
int cmp = key.compareTo(x.key);
if (cmp == 0) return x;
if (cmp < 0) return floor(x.left, key);
//cmp>0,x就是备选方案
TreeNode t = floor(x.right, key);
if (t != null) return t;
else return x;
}
/**
* 获取大于等于key的最小键
*
* @param key 键
* @return 大于等于key的最小键
*/
public K ceiling(K key) {
if (key == null) {
throw new IllegalArgumentException("key cannot be null!");
}
TreeNode x = ceiling(root, key);
if (x != null) return x.key;
return null;
}
private TreeNode ceiling(TreeNode x, K key) {
//边界条件,如果找到叶子结点了还没找到就返回null
if (x == null) return null;
int cmp = key.compareTo(x.key);
if (cmp == 0) return x;
if (cmp > 0) return ceiling(x.right, key);
//cmp<0,x就是备选方案
TreeNode t = ceiling(x.left, key);
if (t != null) return t;
else return x;
}
/**
* 获取key在树中排名
*
* @param key 键
* @return key在树中排名
*/
public int rank(K key) {
if (key == null) {
throw new IllegalArgumentException("key cannot be null!");
}
return rank(root, key);
}
//rank本质上求的是小于key的元素个数
private int rank(TreeNode x, K key) {
//边界条件,注:排名是从0开始的
if (x == null) return 0;
int cmp = key.compareTo(x.key);
if (cmp == 0) return size(x.left);
if (cmp < 0) return rank(x.left, key);
//cmp > 0
return size(x.left) + 1 + rank(x.right, key);
}
/**
* 获取排名为 k 的键
*
* @param k 排名
* @return 排名为k的键
*/
public K select(int k) {
if (k < 0 || k > size()) {
throw new IllegalArgumentException("k=" + k + ", size=" + size());
}
return select(root, k).key;
}
private TreeNode select(TreeNode x, int k) {
if (x == null) return null;
int rank = size(x.left);
if (k == rank) return x;
if (k < rank) return select(x.left, k);
// k > rank
return select(x.right, k - rank - 1);
}
/**
* 获取>=low&&<=high的键值对的个数
*
* @param low 下界
* @param high 上界
* @return >=low&&<=high的键值对的个数
*/
public int size(K low, K high) {
if (low == null || high == null) {
throw new IllegalArgumentException("low and high cannot be null!");
}
if (low.compareTo(high) > 0) {
throw new IllegalArgumentException("low cannot be greater than high!");
}
int k1 = rank(low);
int k2 = rank(high);
if (contains(high)) return k2 - k1 + 1;
else return k2 - k1;
}
/**
* 获取>=low&&<=high的键的集合
*
* @param low 下界
* @param high 上界
* @return >=low&&<=high的键的集合
*/
public Set<K> keys(K low, K high) {
if (low == null || high == null) {
throw new IllegalArgumentException("low and high cannot be null!");
}
if (low.compareTo(high) > 0) {
throw new IllegalArgumentException("low cannot be greater than high!");
}
Set<K> set = new LinkedHashSet<>();
keys(root, low, high, set);
return set;
}
private void keys(TreeNode x, K low, K high, Set<K> set) {
if (x == null) return;
//BST的中序遍历是有序
int cmplo = x.key.compareTo(low);
int cmphi = x.key.compareTo(high);
//遍历左子树(剪枝)
//当前结点比low大,说明当前结点的左子树中可能存在大于low的元素
if (cmplo > 0) keys(x.left, low, high, set);
//遍历根节点
if (cmplo >= 0 && cmphi <= 0) set.add(x.key);
//遍历右子树(剪枝)
//当前结点比high小,说明当前结点的右子树中可能存在小于high的元素
if (cmphi < 0) keys(x.right, low, high, set);
}
/***********************************************************
* Helper methods
***********************************************************/
/**
* 判断结点是否为红色
*
* @param node 所要判断的结点
* @return 红色返回true,黑色返回false
*/
private boolean isRed(TreeNode node) {
if (node == null) return false;
return node.color;
}
/**
* 计算结点的size
*
* @param node 要计算的结点
* @return 结点的size
*/
private int size(TreeNode node) {
//为了避免空指针异常
if (node == null) return 0;
return node.size;
}
/**
* 左旋
*
* @param h
* @return
*/
private TreeNode rotateLeft(TreeNode h) {
TreeNode x = h.right;
h.right = x.left;
x.left = h;
x.color = h.color;
h.color = RED;
//改变size
x.size = h.size;
h.size = size(h.left) + size(h.right) + 1;
return x;
}
/**
* 右旋
*
* @param h
* @return
*/
public TreeNode rotateRight(TreeNode h) {
TreeNode x = h.left;
h.left = x.right;
x.right = h;
x.color = h.color;
h.color = RED;
//改变size
x.size = h.size;
h.size = size(h.left) + size(h.right) + 1;
return x;
}
/**
* 反转颜色
*
* @param node
*/
private void filpColor(TreeNode node) {
node.color = !node.color;
node.left.color = !node.left.color;
node.right.color = !node.right.color;
}
/**
* x的右孩子如果是2结点的情况,向x的左孩子借结点的过程
*
* @param x
* @return
*/
//调用这个方法时要确定x不是2结点且x的右孩子是2结点
private TreeNode moveRedRight(TreeNode x) {
filpColor(x);
//如果x的左孩子是3结点
if (isRed(x.left.left)) {
x = rotateRight(x);
filpColor(x);
}
return x;
}
//左孩子向右孩子借结点
private TreeNode moveRedLeft(TreeNode x) {
filpColor(x);
//如果x的右孩子是2结点
if (isRed(x.right.left)) {
x.right = rotateRight(x.right);
x = rotateLeft(x);
filpColor(x);
}
return x;
}
/**
* 自底向上修复
*
* @param x
* @return
*/
private TreeNode fixUp(TreeNode x) {
//如果有右倾结点,左旋,改成左倾
if (isRed(x.right)) x = rotateLeft(x);
//如果有连续的左倾结点,右旋
if (isRed(x.left) && isRed(x.left.left)) x = rotateRight(x);
//自顶向下分解4-node
if (isRed(x.left) && isRed(x.right)) filpColor(x);
//更新size
x.size = size(x.left) + size(x.right) + 1;
return x;
}
/**********************************************************
* Check methods
**********************************************************/
public boolean check() {
//判断本树是否是2-3树
boolean is23 = is23();
if (!is23) System.err.println("The Tree is not 23!");
//判断本树是否满足黑高平衡
boolean isBalanced = isBalanced();
if (!isBalanced) System.err.println("The Tree is not balanced!");
return is23 && isBalanced;
}
/**
* 判断是否是2-3树
*
* @return
*/
private boolean is23() {
return !isRed(root) && is23(root);
}
private boolean is23(TreeNode node) {
if (node == null) return true;
//有右倾的红色连接
if (isRed(node.right)) return false;
//有连续的左倾的红色连接
if (isRed(node.left) && isRed(node.left.left)) return false;
return is23(node.left) && is23(node.right);
}
/**
* 判断本树是否满足黑高平衡
*
* @return
*/
private boolean isBalanced() {
//先求根节点到最左边叶子结点的黑高
int black = 0;
TreeNode x = root;
while (x != null) {
if (!isRed(x)) black++;
x = x.left;
}
//验证所有从根节点到叶子结点的黑高是否和black相等
return isBalanced(root, black);
}
private boolean isBalanced(TreeNode node, int black) {
if (node == null) return black == 0;
if (!isRed(node)) black--;
return isBalanced(node.left, black) && isBalanced(node.right, black);
}
public static void main(String[] args) {
RedBlackTree<Character, String> tree = new RedBlackTree<>();
String value = "刘亦菲";
tree.put('A', value);
tree.put('S', value);
tree.put('E', value);
tree.put('R', value);
tree.put('C', value);
tree.put('D', value);
tree.put('I', value);
tree.put('N', value);
tree.put('B', value);
tree.put('X', value);
// V get(K key), boolean contains(K key)
/*System.out.println(tree.get('D'));
System.out.println(tree.get('0'));
System.out.println(tree.contains('D'));
System.out.println(tree.contains('0'));*/
/*System.out.println(tree.size());
System.out.println(tree.isEmpty());
tree.clear();
System.out.println(tree.size());
System.out.println(tree.isEmpty());*/
// void deleteMax()
/*System.out.println(tree.size());
System.out.println(tree.contains('X'));
tree.deleteMax();
System.out.println(tree.size());
System.out.println(tree.contains('X'));
tree.clear();*/
//tree.deleteMax();
// void deleteMin()
//System.out.println(tree.size());
/*System.out.println(tree.contains('A'));
tree.deleteMin();
System.out.println(tree.size());
System.out.println(tree.contains('A'));
tree.clear();
tree.deleteMin();*/
// void delete(K key)
/*System.out.println(tree.size());
System.out.println(tree.contains('B'));
tree.delete('B');
System.out.println(tree.size());
System.out.println(tree.contains('B'));
System.out.println(tree.size());
System.out.println(tree.contains('C'));
tree.delete('C');
System.out.println(tree.size());
System.out.println(tree.contains('C'));
System.out.println(tree.size());
System.out.println(tree.contains('E'));
tree.delete('E');
System.out.println(tree.size());
System.out.println(tree.contains('E'));
System.out.println(tree.size());
System.out.println(tree.contains('0'));
tree.delete('0');
System.out.println(tree.size());
System.out.println(tree.contains('0'));*/
/*System.out.println(tree.Min()); // A
System.out.println(tree.Max()); // X*/
// K floor(K key), K ceiling(K key)
/*System.out.println(tree.floor('D'));
System.out.println(tree.floor('F'));
System.out.println(tree.floor('0'));
System.out.println(tree.ceiling('D'));
System.out.println(tree.ceiling('M'));
System.out.println(tree.ceiling('Z'));*/
// int rank(K key), K select(int k)
/*System.out.println(tree.rank('A'));
System.out.println(tree.rank('X'));
System.out.println(tree.rank('0'));
System.out.println(tree.rank('F'));
System.out.println(tree.rank('Z'));*/
/*System.out.println(tree.select(1)); // B
System.out.println(tree.select(9)); // X
System.out.println(tree.select(4)); // E*/
// int size(K low, K high)
/*System.out.println(tree.size('C', 'N'));
System.out.println(tree.size('A', 'X'));*/
// Set<K> keys(K low, K high)
System.out.println(tree.keys('C', 'N'));
// Set<K> keys()
System.out.println(tree.keys());
tree.clear();
System.out.println(tree.keys());
}
}
················································································································································································································
扩展 —— B树
B树,又称多路平衡查找树,B树中所有结点的孩子结点数的最大值又称为B树的阶,
通常用m表示。一棵m阶B树或为空树,或为满足如下特性的m叉树:
- 树中每个结点至多有m棵子树(即至多含有m-1个关键字)
- 若根结点不是叶子结点(null),则至少有两棵子树 (null)
- 除根结点外的所有非叶结点至少有┌m/2┐棵子树(即至少含有┌m/2┐ -1个关键字)
- 所有非叶子结点的结构如下:
其中,Ki (i=1, 2, …, n) 为结点的关键字,且满足K1 < K2 < … < Kn,Pi (i=1, 2, …, n) 为指向子树根结点的指针,且指针Pi-1所指子树中所有结点的关键字均小于Ki,Pi 所指子树中所有结点的关键字均大于Ki, n (┌m/2┐ -1 <= n <= m-1) 为结点中关键字的个数。
- 所有叶子结点都出现在同一层次上,并且不带信息。(完美平衡)
B树阶:每一个结点最大的度。
根节点:度[2, m]
其余结点: 度[m/2, m]
查找树
完美平衡
比如这就是一棵3阶B树。当然,通常B树的阶都很大 (几百甚至几千)。
有发现什么吗?
2-3树就是3阶B树,2-3-4树就是4阶B树![2, 4]
这意味着红黑树其实就是一棵特殊的3阶或者4阶B树!