一般的二叉搜索树的高度并没有得到有效限制,在极端情况下,他甚至可以退化为一个链表。而对树高进行限制以达到O(logn)高度的二叉搜索树,又称为平衡搜索树。AVL树是最先发明的自平衡二叉查找树,它的名字来源于它的发明者 G.M. Adelson-Velsky 和 E.M. Landis 。
平衡搜索树因为树高控制在O(logn)的范围内,查找操作用时O(logn)。进行插入和删除操作时,可能会破坏树的平衡性质,需要进行重平衡操作(rebalance)。
定义
首先定义结点的平衡因子,等于左子树高度减去右子树高度。AVL树的每个结点的平衡因子取值只有-1, 0, 1三种取法,也就是说任意结点的左右孩子高度差不超过一。
AVL树的高度
为了说明AVL树的高度和结点数的关系,我们从相反的角度,考虑高度为h的AVL树最少包含多少个结点。
令高度为h的AVL树包含的最少结点是T(h),则高度为h-1的AVL树最少结点是T(n-1),高度为h-2的AVL树最少结点是T(n-2)。接下来用递归的思想来考虑如何得到一棵高度h的AVL树。
显然,树的左右孩子也必须是AVL树。由于总高度为h,因此左孩子或右孩子中的较大者高度为h-1,另一个孩子高度为h-1或h-2 ,由于我们要计算最少结点数,另一个孩子高度取h-2
由此得到递推关系式: T(h) = T(h-1) + T(h-2) + 1 ,初始条件 T(1) = 1 , T(2) = 2
对两边同时+1 ,得 T(h) + 1 = T(h-1)+1 + T(h-2) + 1
令S(h) = T(h) + 1 , S(h) = S(h-1) + S(h-2) ,S(1) = 2, S(2) = 3
对比斐波那契数列 0 1 1 2 3 5
可得 S(h) = F(h+2)
T(h) = F(h+2) – 1
而F(n)的通项中n在指数上,由此可得,节点数为n时,高度为O(logn)量级。
查找
AVL的查找可以直接使用BST的查找,算法不变。由于树高得到控制,查找时间复杂度为O(logn)
插入
插入后失去平衡的情况有4种,重平衡的过程也可以通过单旋和双旋来完成。
首先来看简单的单旋:
左左情况 rotateLL
某结点因为其左孩子的左子树插入导致失衡,在这里叫做rotateLL过程。
代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
template <typename T> void AVL<T>::rotateLL(BinNodePosi(T) t1, BinNodePosi(T) t2) { //t2 --> st2 t2->lchild = t1->rchild; //st2 --> t1 if (t1->rchild) t1->rchild->parent = t2; //t1 --> t2 t1->rchild = t2; //父节点 --> t1(树根情况) if (t2->parent == NULL) { t1->parent = NULL; this->_root = t1; t2->parent = t1; return; } //t1 --> 父节点 t1->parent = t2->parent; //父节点 --> t1(非树根) if (t2->parent->lchild == t2) t2->parent->lchild = t1; else t2->parent->rchild = t1; //t2 --> t1 t2->parent = t1; } |
右右情况 rotateRR
某结点因为其右孩子的右子树插入导致失衡,在这里叫做rotateRR过程。
代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
template <typename T> void AVL<T>::rotateRR(BinNodePosi(T) t2, BinNodePosi(T) t1) { //t1 --> st2 t1->rchild = t2->lchild; //st2 --> t1 if (t2->lchild) t2->lchild->parent = t1; //t2 --> t1 t2->lchild = t1; //父节点 --> t2(树根) if (t1->parent == NULL) { this->_root = t2; t2->parent = NULL; t1->parent = t2; return; } //t2 --> 父节点 t2->parent = t1->parent; //父节点 --> t2(非树根) if (t1->parent->lchild == t1) t1->parent->lchild = t2; else t1->parent->rchild = t2; //t1 --> t2 t1->parent = t2; } |
左右情况
某结点的左孩子的右子树插入导致失衡。此时需要进行两次单旋。
右左情况
某结点的右孩子的左子树插入导致失衡。此时需要进行两次单旋。
此情况与左右情况对称,此处略去。
使用旋转重平衡
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
template <typename T> void AVL<T>::insertFixUp(BinNodePosi(T) p) { BinNodePosi(T) q; q = tallerChild(p); q = tallerChild(q); if (p->lchild && q == p->lchild->lchild) { rotateLL(q->parent, p); } else if (p->rchild && q == p->rchild->rchild) { rotateRR(q->parent, p); } else if (p->lchild && q == p->lchild->rchild) { rotateRR(q, q->parent); this->updateHeightAbove(q->lchild); rotateLL(p->lchild, p); } else { rotateLL(q, q->parent); this->updateHeightAbove(q->rchild); rotateRR(p->rchild, p); } this->updateHeightAbove(p); } |
3+4重构
上面通过旋转来使树恢复平衡的过程比较繁琐。实际上可以通过观察发现,不管怎么旋转,调整后的树仍然要满足中序遍历下的大小关系。
例如上面的左右情况,旋转最终的结果的拓扑连接总是下图这样:
因此,直接将这3个结点,4个子树连接成上图的样子,既方便又明了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
template <typename T> void AVL<T>::insertFixUp2(BinNodePosi(T) p) { BinNodePosi(T) q; BinNodePosi(T) mid; mid = tallerChild(p); q = tallerChild(mid); BinNodePosi(T) par = p->parent; enum {ROOT, LEFT, RIGHT} ptoc; if (p->parent) { if (p == p->parent->lchild) ptoc = LEFT; else ptoc = RIGHT; } else ptoc = ROOT; BinNodePosi(T) t1; BinNodePosi(T) t2; BinNodePosi(T) t3; BinNodePosi(T) st1; BinNodePosi(T) st2; BinNodePosi(T) st3; BinNodePosi(T) st4; if (p->lchild && q == p->lchild->lchild) { t1 = q; t2 = q->parent; t3 = p; st1 = q->lchild; st2 = q->rchild; st3 = mid->rchild; st4 = p->rchild; } else if (p->rchild && q == p->rchild->rchild) { t1 = p; t2 = mid; t3 = q; st1 = p->lchild; st2 = mid->lchild; st3 = q->lchild; st4 = q->rchild; } else if (p->lchild && q == p->lchild->rchild) { t1 = mid; t2 = q; t3 = p; st1 = mid->lchild; st2 = q->lchild; st3 = q->rchild; st4 = p->rchild; } else { t1 = p; t2 = q; t3 = mid; st1 = p->lchild; st2 = q->lchild; st3 = q->rchild; st4 = mid->rchild; } switch (ptoc) { case ROOT : _root = t2; break; case LEFT : par->lchild = t2; break; case RIGHT : par->rchild = t2; break; } t2->parent = par; connect34(t1, t2, t3, st1, st2, st3, st4); } template <typename T> void AVL<T>::connect34(BinNodePosi(T) t1, BinNodePosi(T) t2, BinNodePosi(T) t3, BinNodePosi(T) st1, BinNodePosi(T) st2, BinNodePosi(T) st3, BinNodePosi(T) st4) { t1->lchild = st1; if (st1) st1->parent = t1; t1->rchild = st2; if (st2) st2->parent = t1; t3->lchild = st3; if (st3) st3->parent = t3; t3->rchild = st4; if (st4) st4->parent = t3; t2->lchild = t1; t1->parent = t2; t2->rchild = t3; t3->parent = t2; this->updateHeightAbove(t1); this->updateHeightAbove(t3); } |
删除
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
template <typename T> bool AVL<T>::remove(const T & e) { BinNodePosi(T) x = search(e); if (!x) return false; removeAt(x); BinNodePosi(T) p = _hot; BinNodePosi(T) q; while (p != NULL) { q = p; p = p->parent; if (!AvlBalanced(q)) { insertFixUp2(q); } } return true; } |