Balanced Search Tree 平衡搜索树
0. 前言
上一篇文章,我们分析了二叉搜索树(BST传送门)。在二叉搜索树中,查找、插入、删除、ceiling和floor等操作的平均时间性能取决于树高。在极端情况下(如插入有序序列后),如果树高h过大,其时间性能将会变得很差。根据树的特点,通过平衡树操作可以有效地降低树的高度,从而达到理想的时间性能。这就是本篇文章的重点——平衡搜索树(BSTs)。
1. 2-3搜索树
基本概念
2-3树的是由若干个2-节点(2-node)和3-节点(3-node)构成的树。其基本特征是有序对称(symmetric order)和绝对平衡(perfect balance)。
2-节点:2-节点具有一个key和两个子链接
3-节点:3-节点具有两个key和三个子链接
有序对称:通过中序遍历将得到递增的序列(升序序列)
绝对平衡:从根节点到空链接的路径都是相同长度的
基本操作
查找操作:对比节点key值,如果大于key,查右子树;如果小于key,查左子树;直到等于key或查到空链接。特别地,对于3-节点,如果小于左key,查左子树;如果大于左key小于右key,查中子树;如果大于右key,查右子树;直到等于key或查到为空链接。
插入操作:查找到该节点key值的合适位置,插入节点。如果该节点变成3-节点,无需操作;如果该节点变成4-节点,则要将中间key值的节点上浮到父节点,以此类推。在上浮过程中,如果root节点成为4-节点,则中间key值节点成为新root节点,左右key值节点分离成为新root节点的子节点。
下图示意了三种上浮的情况:
性能分析
操作、插入、删除操作均~clgN,其中系数c取决于实现方法(0< c <1)
分析所得,最坏的情况下是lgN,做好的情况下是log3N(约为0.631 lgN)。
存在问题
现实中,直接实现2-3树是非常复杂的,主要有以下原因:
- 维持多种类型的节点是冗余的(至少需要维持2-节点、3-节点和4-节点)
- 需要多种比较才能降低树高
- 需要增加树高以分离4-节点
- 分离节点时的情况太多
2. (左倾)红黑树
基本概念
上一节分析了2-3树存在的问题就是实现起来非常复杂,因此引入了(左倾)红黑树来表示2-3树。(这样的话,实现红黑树就相当于实现了2-3树)
在红黑树中,存在以下特征:(结合下图)
- 没有节点连接两条红链接
- 每一条从根到空链接的路径中黑链接的数量的相等的
- 红链接都是左倾的
当理解红黑树的特征之后,接着看看用红黑树来表示2-3树的方法:将红黑树中左倾的红链接水平放置,那么红链接所连接的两节点就构成了2-3树的3-节点,其他的构成2-节点。(结合下图)
基本操作
节点表示Node
由于每一个节点仅有一个链接指向父节点,因此对此链接标色,以区别红黑链接
private static final boolean RED = true;
private static final boolean BLACK = false;
private class Node{
Key key;
Value val;
Node left, right;
boolean color; // 父链接的标色
}
private boolean isRed(Node x){
if (x == null) return false;
return x.color == RED;
}
左旋left rotation
private Node rotateLeft(Node h){
assert isRed(h.right);//判断右链接是否红,即是否右倾的情况
Node x = h.right;
h.right = x.left;
x.left = h;
x.color = h.color;
h.color = RED;
return x;
}
右旋right rotation
private Node rotateRight(Node h){
assert isRed(h.left);//判断左链接是否红,即是否左倾的情况
Node x = h.left;
h.left = x.right;
x.right = h;
x.color = h.color;
h.color = RED;
return x;
}
跳色color flip
private void flipColors(Node h){
assert !isRed(h);//判断
assert isRed(h.left);
assert isRed(h.right);
h.color = RED;
h.left.color = BLACK;
h.right.color = BLACK;
}
通过左旋、右旋、跳色等操作,可以保持红黑树的对称有序和绝对平衡的特点。
查找
基于红黑树实现的BSTs的查找操作与基本的BST的查找操作是一致的。
public Val get(Key key){
Node 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.val;
}
return null;
}
插入
当插入一个节点(节点C)到一棵红黑树中,为了保持绝对平衡和对称有序的特点,存在以下情况:
插入的节本操作可以描述成:(结合下图)
- 基本的BST插入操作,同时新插入的节点的附链接标成红色
- 如果需要的话,左右旋转去平衡4-节点(一个节点同时连接两个红链接)
- 通过跳色操作将红链接往上一层传递
- 如果需要的话,旋转以保持所有的红链接左倾
- 循环操作直到满足红黑树的所有特征
总结起来,在递归的场景下,就是以下三种情况:
- 右链接红,左链接黑:左旋
- 左链接红,左子节点的左链接红:右旋
- 左右链接红:跳色
private Node put(Node h, Key key, Value val){
if (h == null) return new Node(key, val, RED);//插入节点(递归出口)
int cmp = key.compareTo(h.key);
if (cmp < 0) h.left = put(h.left, key, val);
else if (cmp > 0) h.right = put(h.right, key, val);
else h.val = val;
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;
}
性能分析
通过红黑树实现的BST,其树高在通常情况下 ~lgN,在最坏的情况下不超过 ~2lgN。
应用
JAVA:java.util.TreeMap ,java.util.TreeSet等数据结构
C++ STL:map, mutilmap, mutilset等数据结构
linux内核
3. B树
基本概念
B树是2-3树的泛化:在B树中,每个节点允许有M-1个子节点(子链接)。(M的取值视实际情况而定)
B树的特征如下:(结合下图)
- 跟节点至少有两个子链接
- 其他节点至少有M/2个子链接
- 外部节点包含内部节点的key(叶节点就是外部节点)
- 内部节点包含每个外部节点的首个key(用于索引)
基本操作
查找
- 从根节点开始
- 通过内部节点对比key,确定对应的链接
- 通过链接确定存储key的外部节点
插入
- 先查找新key的位置
- 插入
- 如果溢出,则要分离节点,并将首key存储在上一级的内部节点
应用
B树被广泛地应用在各操作系统的文件系统和数据库中。如windows的NTFS、Mac的HFS和SQL、ORACLE等各种主流数据库。