SBT
全称:Size Balance Tree
SBT 本质上是一棵二叉搜索树,即对于每一个节点都有:以它右孩子构成的右子树中的所有元素大于它,左孩子为根构成的左子树所有元素都小于它。
与AVL树对比
与 AVL 树不同之处在于 平衡标准
- AVL 树要求左右两棵子树的高度差不超过1
- SB 树则要求每棵子树的大小,不小于其兄弟的子树大小,即每棵叔叔树的大小,不小于其任何侄子树的大小
- 这里的大小
size
,指的是以它为根的子树中节点的数量
- 这里的大小
需要平衡的四种情况
观察一下上面的图,我们想知道T这棵树是否平衡,为什么不判断 L.size == R.size
?这其实是有问题的,因为可能L的节点都落在了A上,R的节点都落在了C上。这同样是不平衡的,但这种情况除非我们继续往下递归,否则很难识别。
所以这里换了一种方法,我们判断R和A、B的size的关系,以及L和C、D size的关系。我们要求L.size >= C.size, D.size
,R.size >= A.size, B.size
。这样就能保证两颗树的规模差不多
了解了判断平衡的方法,我们就可以推出不平衡的情况,一共有四种:
- Size of A > size of R
- Size of B > size of R
- Size of C > size of L
- Size of D > size of L
由于我们使用递归来维护树的平衡性的时候,是从底往上的。因此我们可以假设ABCDLR这六棵子树都是平衡的,这样可以简化我们的分析。
我们假设我们现在有了一个函数叫做 maintain
,它可以将一棵不平衡的子树旋转到平衡状态,什么时候调用maintain
?某个节点所在子树大小发生变化时(因为子树大小发生变换很可能会导致不平衡现象的出现)。
和 AVL 树一样,SBT 也有 LL、LR、RL、RR 四种不平衡的情况
-
LL型:当前节点
T
的左孩子L
的左孩子A
比右孩子R
大- 需要进行一次右旋操作:
-
可以看到,在旋转过程中,节点
L
和T
所在子树大小发生变化了的,所以我们需要再针对它们调用一次maintain
,伪代码:rightRotate(T); maintain(T); maintain(L);
-
LR型:当前节点
T
的左孩子L
的右孩子B
的大小比右孩子R
大- 需要先左旋,再右旋:
-
可以看到,在旋转过程中,节点
L
、T
、B
所在子树大小发生变化,所以需要再调用三次maintain
,伪代码:leftRotate(L); rightRotate(T); maintain(L); maintain(T); maintain(B);
-
RL型:当前节点
T
的右孩子R
的左孩子C
的大小比左孩子L
大- 操作同 LR 型
-
RR型:当前节点
T
的右孩子R
的右孩子D
的大小比左孩子L
大- 操作同 LL 型
代码实现
public class SizeBalancedTreeMap {
public static class SbtNode<K extends Comparable<K>, V> {
public K key;
public V value;
public SbtNode<K, V> left;
public SbtNode<K, V> right;
public int size;
public SbtNode(K key, V value) {
this.key = key;
this.value = value;
size = 1;
}
}
public static class SizeBalancedTree<K extends Comparable<K>, V> {
private SbtNode<K, V> root;
/**
* 右旋操作
* @param cur 右旋之前的根节点
* @return 右旋之后的根节点
*/
private SbtNode<K, V> rightRotate(SbtNode<K, V> cur) {
SbtNode<K, V> leftNode = cur.left;
cur.left = leftNode.right;
leftNode.right = cur;
leftNode.size = cur.size;
cur.size = (cur.left != null ? cur.left.size : 0) + (cur.right != null ? cur.right.size : 0);
return leftNode;
}
/**
* 左旋操作
* @param cur 左旋之前的根节点
* @return 左旋之后的根节点
*/
private SbtNode<K, V> leftRotate(SbtNode<K, V> cur) {
SbtNode<K, V> rightNode = cur.right;
cur.right = rightNode.left;
rightNode.left = cur;
rightNode.size = cur.size;
cur.size = (cur.right != null ? cur.right.size : 0) + (cur.left != null ? cur.left.size : 0);
return rightNode;
}
/**
* 如果失衡,则进行平衡性调整
* @param cur 要调整的子树根节点
* @return 新的子树根节点
*/
private SbtNode<K, V> maintain(SbtNode<K, V> cur) {
if (cur == null) {
return null;
}
if (cur.left != null && cur.left.left != null && cur.right != null
&& cur.left.left.size > cur.right.size) {
// LL
cur = rightRotate(cur);
cur.right = maintain(cur.right);
cur = maintain(cur);
} else if (cur.left != null && cur.left.right != null && cur.right != null
&& cur.left.right.size > cur.right.size){
// LR
cur.left = leftRotate(cur.left);
cur = rightRotate(cur);
cur.left = maintain(cur.left);
cur.right = maintain(cur.right);
cur = maintain(cur);
} else if (cur.right != null && cur.right.right != null && cur.left != null
&& cur.right.right.size > cur.left.size) {
// RR
cur = leftRotate(cur);
cur.left = maintain(cur.left);
cur = maintain(cur);
} else if (cur.right != null && cur.right.left != null && cur.left != null
&& cur.right.left.size > cur.left.size) {
// RL
cur.right = rightRotate(cur.right);
cur = leftRotate(cur);
cur.right = maintain(cur.right);
cur.left = maintain(cur.left);
cur = maintain(cur);
}
return cur;
}
}
}
红黑树
与 Avl 树的高度、SBT 的大小(规模)一样,红黑树节点也记录着一个标签:颜色。
红黑树规则:
- 每一个节点要么是黑色,要么是红色
- 根节点和叶节点(即最底层的
null
节点)都是黑色 - 红节点不相邻
- 从某一个头部
cur
出发,每条到结束节点的路径上,黑色节点的数量一样多
不过红黑树操作太过复杂,面试基本不会考,简单了解一下就行