AVL樹

  二叉查找樹在極端狀況下,會退化爲鏈表,好比一個排好序的數組,構建成二叉樹後,就是一顆所有左傾或右傾的樹,這時候查找的時間爲O(N),AVL樹是帶有平衡條件的二叉查找樹,它的每一個節點左子樹和右子樹的高度最多相差1,它會保證樹的高度爲O(logN),因此在查找時,能保證最壞狀況下時間爲O(logN),AVL樹節點中須要一個成員存儲高度屬性。node

  AVL樹節點能夠定義以下:數組

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

  計算節點高度代碼以下:spa

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,計算一個節點的高度差代碼以下:3d

 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樹,紅黑樹維持平衡的基礎操做。

       

旋轉後依然維持二叉樹性質code

左旋轉代碼以下:blog

 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 }
當插入或刪除節點時,有四種樹不平衡的狀況,下面逐一討論。
1、LL和LR
設某節點N在插入或刪除後,其節點高度差爲factor ,若是factor的值大於1,則繼續判斷節點N的左子節點高度差,若是左子節點高度差大於0,咱們稱這種狀況爲LL,若是左子節點高度差小於0,則稱之爲LR。

    

以上圖所示,在插入節點D後,致使A節點左邊高度爲2,右邊高度爲0,致使失去平衡,對A右旋轉便可恢復平衡class

而對於LR的情形,在插入E節點後,A失去平衡,這時候,先對A的左子節點,也就是B進行左旋轉,轉化爲LL的情形,再對A進行右旋轉。基礎

2、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 }
相關文章
相關標籤/搜索