AVL树

  二叉查找树在极端情况下,会退化为链表,比如一个排好序的数组,构建成二叉树后,就是一颗全部左倾或右倾的树,这时候查找的时间为O(N),AVL树是带有平衡条件的二叉查找树,它的每个节点左子树和右子树的高度最多相差1,它会保证树的高度为O(logN),所以在查找时,能保证最坏情况下时间为O(logN),AVL树节点中需要一个成员存储高度属性。

  AVL树节点可以定义如下:

1 struct AVLTreeNode {
2     AVLTreeNode* pLeft;
3     AVLTreeNode* pRight;
4     int nData;
5     int height;
6 };

  计算节点高度代码如下:

1 int AVLTreeHeight(AVLTreeNode* pNode) {
2     if (pNode == nullptr)
3         return 0;
4     else {
5         return Max(AVLTreeHeight(pNode->pLeft), AVLTreeHeight(pNode->pRight)) + 1;
6     }
7 }

  要维护树的平衡,每个节点左右节点的高度差不能超过1,计算一个节点的高度差代码如下:

 1 int AVLTreeNodeFactor(AVLTreeNode* pNode) {
 2     //left sub node's height - right sub node's height
 3     if (pNode == nullptr)
 4         return 0;
 5 
 6     int leftHeight = AVLTreeHeight(pNode->pLeft);
 7     int rightHeight = AVLTreeHeight(pNode->pRight);
 8 
 9     return leftHeight - rightHeight;
10 }
在讨论如何修复树的平衡之前,先来讨论下旋转,旋转是对树的修正,是AVL树,红黑树维持平衡的基础操作。

       

旋转后依然维持二叉树性质

左旋转代码如下:

 1 AVLTreeNode* AVLTreeNodeLeftRotate(AVLTreeNode* pNode) {
 2     if (pNode == nullptr)
 3         return nullptr;
 4 
 5     AVLTreeNode* pTemp = pNode->pRight;
 6     pNode->pRight = pTemp->pLeft;
 7     pTemp->pLeft = pNode;
 8 
 9     //recalc pTemp and pNode 's height
10     pTemp->height = AVLTreeHeight(pTemp);
11     pNode->height = AVLTreeHeight(pNode);
12 
13     return pTemp;
14 }

 右旋转代码如下:

 1 AVLTreeNode* AVLTreeNodeRightRotate(AVLTreeNode* pNode) {
 2     if (pNode == nullptr)
 3         return nullptr;
 4 
 5     AVLTreeNode* pTemp = pNode->pLeft;
 6     pNode->pLeft = pTemp->pRight;
 7     pTemp->pRight = pNode;
 8 
 9     //recalc pTemp and pNode 's height
10     pTemp->height = AVLTreeHeight(pTemp);
11     pNode->height = AVLTreeHeight(pNode);
12     return pTemp;
13 }
当插入或删除节点时,有四种树不平衡的情况,下面逐一讨论。
一、LL和LR
某节点在插入或删除后,可能会影响到父节点或更高层的节点的高度差 ,如果高度差大于1,则继续判断高度差大于1的节点的左子节点高度差,如果左子节点高度差大于0,我们称这种情况为LL,如果左子节点高度差小于0,则称之为LR。

    

以上图所示,在插入节点D后,导致A节点左边高度为2,右边高度为0,导致失去平衡,对A右旋转即可恢复平衡

而对于LR的情形,在插入E节点后,A失去平衡,这时候,先对A的左子节点,也就是B进行左旋转,转化为LL的情形,再对A进行右旋转。

二、RR和RL

  这两种情形只是LL和LR的镜像问题,取对称操作 就可以了。LL和RR这两种情况是不平衡发生在外侧,而LR和RL这两种情况是不平衡发生在树内侧

  拿代码来表示这四种情形如下:AVLTreeReblance的参数pNode为失去平衡的节点,返回的节点是在旋转前以pNode为根节点的子树在旋转后新的子树根节点。就拿LR为例,传入的参数是A节点,返回的参数是B节点。

 1 AVLTreeNode* AVLTreeReblance(AVLTreeNode* pNode) {
 2     if (pNode == nullptr)
 3         return nullptr;
 4 
 5     int nFactor = AVLTreeNodeFactor(pNode);
 6     if (nFactor > 1) {
 7         //LL or LR
 8         if (AVLTreeNodeFactor(pNode->pLeft) >= 0)//LL
 9             return AVLTreeNodeRightRotate(pNode);
10         else {//LR
11             //first:left rotate on sub left
12             pNode->pLeft = AVLTreeNodeLeftRotate(pNode->pLeft);
13             //second: right rotate on self
14             return AVLTreeNodeRightRotate(pNode);
15         }
16     }
17     else if (nFactor < -1) {
18         //RR or RL
19         if (AVLTreeNodeFactor(pNode->pRight) <= 0)//RR
20             return AVLTreeNodeLeftRotate(pNode);
21         else {
22             //RL
23             //first:right rotate on sub left
24             pNode->pRight = AVLTreeNodeRightRotate(pNode->pRight);
25             //second: right rotate on self
26             return AVLTreeNodeLeftRotate(pNode);
27         }
28     }
29     else
30         return pNode;
31 }

  AVL树的插入

  AVL树在插入后,就需要更新从新插入节点到根节点路径上那些节点的高度信息,并且对这条路径上的所有节点进行重新平衡。采用递归插入,可以通过递归的回溯来完成。

 1 AVLTreeNode* AVLTreeInsert(AVLTreeNode* pRoot, int nData) {
 2     if (pRoot == nullptr) {
 3         pRoot = new AVLTreeNode;
 4         pRoot->pLeft = pRoot->pRight = nullptr;
 5         pRoot->nData = nData;
 6         pRoot->height = 0;
 7     }
 8     else {
 9         if (nData > pRoot->nData) {
10             pRoot->pRight = AVLTreeInsert(pRoot->pRight, nData);
11         }
12         else if (nData < pRoot->nData) {
13             pRoot->pLeft = AVLTreeInsert(pRoot->pLeft, nData);
14         }
15     }
16     pRoot->height = AVLTreeHeight(pRoot);//更新节点高度
17     pRoot = AVLTreeReblance(pRoot);//对节点重新平衡
18     return pRoot;
19 }

  AVL树的删除

  AVL树的删除跟普通二叉查找树删除流程一样,只是在删除完后,要对新子树根节点进行平衡操作。同样的,路径上的节点高度也要更新,并对被删除节点到根节点做修复

 1 AVLTreeNode* AVLTreeDelete(AVLTreeNode* pRoot, int nData) {
 2     if (pRoot == nullptr)
 3         return nullptr;
 4 
 5     AVLTreeNode* pResult = nullptr;
 6     if (nData > pRoot->nData) {
 7         pRoot->pRight = AVLTreeDelete(pRoot->pRight, nData);
 8         pResult = pRoot;
 9     }
10     else if (nData < pRoot->nData) {
11         pRoot->pLeft = AVLTreeDelete(pRoot->pLeft, nData);
12         pResult = pRoot;
13     }
14     else {
15         if (pRoot->pRight && pRoot->pLeft) {
16             AVLTreeNode* pTemp = AVLTreeMinimumNode(pRoot->pRight);
17             pRoot->nData = pTemp->nData;
18             pRoot->pRight = AVLTreeDelete(pRoot->pRight, pTemp->nData);
19             pResult = pRoot;
20         }
21         else {
22             AVLTreeNode* pTemp = (pRoot->pRight == nullptr)?pRoot->pLeft:pRoot->pRight;
23             delete pRoot;
24             pResult = pTemp;
25         }
26     }
27     if (pResult)
28         pResult->height = AVLTreeHeight(pResult);
29     return AVLTreeReblance(pResult);
30 }

 

posted on 2018-05-04 22:39  凄夜  阅读(236)  评论(0编辑  收藏  举报

导航