高度平衡树(height-balanced tree, hb tree)就是以高度作为平衡条件的树,其实就是AVL树的扩展。我们熟知的AVL树(hb[1] 树)的平衡条件是左右子树高度差不超过1,其实高度差可以是任意正整数。Hb[k]树就是左右子树高度差不超过k的树。
Hb[k]树的插入和删除算法基本上和AVL树是一样的。
在插入一个节点后可能会造成不平衡,只要对最小不平衡树做调整即可。下面就左子树大于右子树的情况进行分析。
如下图,情况可以分成A、B两种:
1) 对于情况A可以通过单旋使树达到平衡:
2) 对于情况B可以通过双旋使树达到平衡:
删除的算法和插入类似,都是对最小不平衡树进行调整。只是调整后树的高度可能会减1,上一级可能成为新的最小不平衡树,就要继续调整,最坏的情况下会一直调整到根节点。对于不平衡树的调整和插入时的差不多,只是比插入多了一种情况C。
对于情况C只需单旋就可以了,而且树的高度没有变化,就不用向上调整了。
其实C的情况可以和A合并为一种,这样插入和删除的平衡算法就可以统一了。
另外对于某些情况其实单旋和双旋都可以用:
如A的某些情况A’可以进行双旋
B的某些情况B’也可以进行单旋
上面两种情况都只会出现在k>=2时,他们的旋转都没有改变树的高度,删除的时候使用比较好。当然插入时也可以使用,但可能就会造成向上递归了,一般不提倡这样做。
经过测试发现hb[2]树的高度和AVL树(hb[1]树)差不多,但hb[2]树旋转的次数却不到AVL树的一半。而Hb[2]树的高度和红黑树也差不多,但旋转的次数只是比红黑树的一半多一点。个人认为hb[2]树的性能会比红黑树好,而且实现起来和AVL树一样简单。
现在再回头看上面A、B、C情况,发现最右边的子树高度n加上一个a值(0<=a<=k-1)后,树的平衡性仍然能够保持。也就是说,并不一定要在高度差为k+1时才去调整,在其他情况下也可以作调整,调整后的树它的左右子树高度差不超过k-a。
上面的结论有着总要的意义,我们可以得到一个更佳的调整策略。因为树的旋转主要发生在矮树上,因此我们可以让矮树“宽松”些,让高树“严谨”些。例如对于hb[2]树,我们可以这样改变:对于高度小于等于7的树,它的子树高度差不超过2,对于高度大于7的树,它的子树高度差不超过1,我们把这样的树称为hb[2,7,1]树。这样hb[2,7,1]树的平衡性可以和hb[1]树相当,性能和hb[2]树相当。个人认为把矮树的高度定义为6或7为最佳。
总的来看hb树其实只是对AVL树一个小小的扩展,牺牲一点的平衡性换来性能上大大的提升。我认为hb[2,7,1]树是其中最佳的选择,而且觉得它在各个方面都有取代红黑树的实力。
package tree;
public class HbTree {
static class Node {
static final Node nullNode = new Node();
int key;
Node left;
Node right;
int height;
public Node() {
}
public Node(int key) {
this.key = key;
this.height = 1;
left = nullNode;
right = nullNode;
}
public void refreshheight() {
height = Math.max(left.height, right.height) + 1;
}
public void print(int k) {
String keyStr;
if (key < 10) {
keyStr = " " + key;
} else if (key < 100) {
keyStr = " " + key;
} else {
keyStr = key + "";
}
System.out.print(keyStr + " ");
if (right != null && right != nullNode) {
right.print(k + 1);
}
if (left != null && left != nullNode) {
System.out.println();
for (int i = 0; i < k; i++) {
System.out.print(" ");
}
left.print(k + 1);
}
}
}
private int k = 1;
private Node root = Node.nullNode;
public HbTree(int k) {
this.k = k < 1 ? 1 : k;
}
private Node upLeft(Node root) {
Node newRoot = root.left;
root.left = newRoot.right;
newRoot.right = root;
root.refreshheight();
newRoot.refreshheight();
return newRoot;
}
private Node upRight(Node root) {
Node newRoot = root.right;
root.right = newRoot.left;
newRoot.left = root;
root.refreshheight();
newRoot.refreshheight();
return newRoot;
}
private Node balance(Node root) {
int k = this.k;
if (k > 1 && root.height > 7) {
k--;
}
if (root.left.height - root.right.height > k) {
if (root.left.left.height >= root.left.right.height) {
root = upLeft(root);
} else {
root.left = upRight(root.left);
root = upLeft(root);
}
} else if (root.right.height - root.left.height > k) {
if (root.right.right.height >= root.right.left.height) {
root = upRight(root);
} else {
root.right = upLeft(root.right);
root = upRight(root);
}
} else {
root.refreshheight();
}
return root;
}
private Node insert(Node root, int key) {
if (root == Node.nullNode || root == null) {
return new Node(key);
} else if (root.key > key) {
Node newNode = insert(root.left, key);
if (newNode == null) {
return null;
} else {
root.left = newNode;
}
} else if (root.key < key) {
Node newNode = insert(root.right, key);
if (newNode == null) {
return null;
} else {
root.right = newNode;
}
} else {
return null;
}
root.refreshheight();
return balance(root);
}
private Node topMin(Node root) {
if (root.left == Node.nullNode) {
return root;
} else {
Node min = topMin(root.left);
root.left = min.right;
min.right = balance(root);
return min;
}
}
private Node remove(Node root, int key) {
if (root == Node.nullNode) {
return null;
} else if (root.key == key) {
if (root.right == Node.nullNode) {
return root.left;
} else {
Node min = topMin(root.right);
min.left = root.left;
return balance(min);
}
} else if (root.key > key) {
Node newLeft = remove(root.left, key);
if (newLeft == null) {
return null;
} else {
root.left = newLeft;
return balance(root);
}
} else {
Node newRight = remove(root.right, key);
if (newRight == null) {
return null;
} else {
root.right = newRight;
return balance(root);
}
}
}
public void print() {
if (root == Node.nullNode) {
System.out.println("empty tree");
return;
}
root.print(1);
System.out.println();
}
public void insert(int key) {
Node newNode = insert(root, key);
if (newNode != null) {
root = newNode;
}
}
public void remove(int key) {
Node newRoot = remove(root, key);
if (newRoot != null) {
root = newRoot;
}
}
public int height() {
return root.height;
}
}