一. 二叉搜索樹(Binary Sort Tree)
二叉搜索樹,又稱爲二叉排序樹(二叉查找樹),它或許是一棵空樹,或許是具有一下性質的二叉樹:
1.若它的左子樹不爲空,則左子樹上所有的節點的值小於根節點的值
2.若它的右子樹不爲空,則右子樹上所有的節點的值都大於根節點的值
3.它的左右子樹也分別是二叉搜索樹
二叉搜索樹的這種特性,使得我們在此二叉樹上查找某個值就很方便了,從根節點開始,若要尋找的值小於根節點的值,則在左子樹上去找,反之則去右子樹查找,直到找到與值相同的節點。插入節點也是一樣的道理,從根節點從發,所要插入的值,若小於根節點則去左子樹尋找該節點所對應的位置,反之則去右子樹尋找,直到找到該節點合適的位置。
二. 二叉平衡搜索樹(AVL)
前面提到二叉搜索樹,二叉搜索樹的特性便於我們進行查找插入刪除等一系列操作,其時間複雜度爲O(logn)
,但是遇見最差的情況,比如以下這棵樹:
這棵樹,說是樹,其實已經退化成鏈表了,但從概念上來看,它仍是一棵二叉搜索樹,只要我們按照逐次增大,如1、2、3、4、5、6的順序構造一棵二叉搜索樹,如上圖。那麼插入的時間複雜度就變成了O(n)
,導致這種糟糕的情況原因是因爲這棵樹極其不平衡,右樹的重量遠大於左樹,因此提出平衡二叉搜索樹的結構,稱爲AVL樹。
平衡二叉樹,它能保持二叉樹的高度平衡,降低二叉樹的高度,減少樹的平均查找長度。
AVL樹的性質:
1. 左子樹與右子樹高度之差的絕對值不超過1
2. 樹的每個左子樹和右子樹都是AVL樹
3. 每一個節點都有一個平衡因子(balance factor),任一節點的平衡因子是-1,0,1(每一個節點的平衡因子=右子樹高度-左子樹高度)
做到了這點,這棵樹看起來就比較平衡了,那麼如何生成一棵AVL樹呢?算法相對來說複雜,隨着新節點的加入,樹自動調整自身結構,達到新的平衡狀態,這就是我們想要的AVL樹。我們分析下,爲什麼樹會失衡?是由於插入了一個新的元素。
在AVL樹中,插入一個節點的過程如下:
AVL樹插入節點:
1. AVL樹首先是二叉搜索樹,我們要根據二叉搜索樹的插入節點方式進行插入
2. AVL樹有判斷該樹是否平衡的因子,我們要根據平衡因子來對樹進行選擇調整
具體步驟:
1. 判斷該樹是不是NULL,若爲NULL,直接插入
2. 若不爲NULL, 找到要插入節點的位置(用pParent標記雙親,方便插入節點)pCur
3. 插入節點pCur
4. 更新pParent的平衡因子,然後判斷該樹是否需要調整
a.若更新後pParent平衡因子爲0的話,pParent在插入之前只有左孩子或者右孩子,此時樹的高度不變,該樹仍然爲AVL樹。
b.若更新後的pParent平衡因子爲1或者-1的話,pParent在插入節點前是葉子節點,此時樹的高度可能發生改變,我們需要從pParent節點開始向上判斷調整其祖先節點。
c.若平衡因子不滿足上面的倆種情況,說明該樹已經不平衡,需要調整,具體情況見下面,局部調整完後,上面的樹已經滿足AVL樹,此時退出即可。
(在下圖中,c)插入節點後,調整平衡因子 -> 插入節點之後->(2)最後一個圖,其Parent的bf應爲1)
插入節點代碼實現如下:
template <class K,class V>
bool AVLTree<K, V>::AVLInsert(K key, V val){
//1.根節點爲空,直接插入
if (_root == NULL) {
_root = new Node(key, val);
return true;
}
//2.根節點不爲空
else{
Node* cur = _root;
Node* parent =NULL;
//a)找到要插入節點的位置
while (cur) {
parent = cur;
if (cur->_key > key)
cur = cur->_left;
else if (cur->_key < key)
cur = cur->_right;
else
return false; //不允許出現重複元素的節點
}
//b)插入新節點
cur = new Node(key, val);
if (parent->_key>key){
parent->_left = cur;
cur->_parent = parent;
}
else{
parent->_right = cur;
cur->_parent = parent;
}
//c)插入完成後,調整平衡因子
while (parent){
if (cur == parent->_left)//插入節點在左子樹父節點bf--,反之++
parent->_bf--;
else
parent->_bf++;
//1)插入新節點後,parent->bf==0;說明高度沒變,平衡,返回
if (parent->_bf == 0)
break;
//2)插入節點後parent->_bf==-1||parent->_bf==1;說明子樹高度改變,則繼續向上調整
else if (parent->_bf == -1 || parent->_bf == 1){
cur = parent;
parent = parent->_parent;
}
//3)插入節點後parent->_bf==-2||parent->_bf==2;說明已經不平衡,需要旋轉
else{
if (parent->_bf == 2){
if (cur->_bf == 1)
RotateL(parent);
else// (cur->_bf == -1)
RotateRL(parent);
}
else//parent->_bf == -2 {
if (cur->_bf == -1)
RotateR(parent);
else// (cur->_bf == 1)
RotateLR(parent);
}
break;
}
}//end while (parent)
return true;
}
}
當樹不平衡時,我們需要做出旋轉調整,有四種調整方法,分別爲:1. 左單旋 2.右單旋 3.左右單旋 4.右左單旋, 具體調整方法見:https://blog.csdn.net/tanrui519521/article/details/80935348
三. 紅黑樹
紅黑樹是自平衡二叉樹中的一種,在插入元素時,二叉樹會自動調整元素的位置,使樹的倆叉維持平衡,紅黑樹最普遍的用途就是用來實現關聯數組。
紅黑樹遵守5個基本命題:
1.節點是紅色或者黑色;
2.根是黑色;
3.所有葉子都是黑色(葉子是NIL節點,空節點);
4.每個紅色節點的倆個子節點都是黑色。(從每個葉子到根的所有路徑上不能有倆個連續的紅色節點);
5.從任一節點到其每個葉子的所有簡單路徑都包含相同的黑色節點。
注意到命題5,“從任一節點到其每個葉子的所有簡單路徑都包含相同數目的黑色節點”,這一點就保證了,紅黑樹的樹高由於每個分支上黑色節點數目一定而維持平衡。