VL樹簡介?
AVL樹是一種自平衡二叉搜索樹,通過平衡因子(Balance Factor, BF)?和旋轉操作,確保樹始終保持平衡,避免退化成鏈表,從而保證查找、插入、刪除的時間復雜度穩定在 ?O(log n)?。
?核心特點?
- ?平衡條件?:每個節點的左右子樹高度差不超過1(BF ∈ {-1, 0, 1})。
- ?調整方法?:通過四種旋轉?(右單旋、左單旋、左右雙旋、右左雙旋)修復失衡。
- ?優勢?:比普通BST更穩定,適合頻繁動態更新的場景。
目錄
VL樹簡介?
?核心特點?
一, 搜索二叉樹的固有問題:
二,平衡因子:
1`基本定義:
2`補充說明:
三,結構的設計:
????????1`擴充的成員變量:?
?? ? ? ? 2`小坑:?
四,元素的插入:
? ? ? ? ?1`插入元素 :
?????????2`平衡因子的維護:
? ? ? ? 3`旋轉:
????????1`右單旋:
????????2`左單旋:
?????? ?3`左右雙旋:
????????4`右左雙旋:
五`元素的查找(按值):
?六`中序遍歷順序輸出:
七`類的結構和成員函數代碼匯總:
一, 搜索二叉樹的固有問題:
- ? ? ? ? 理想情況下 , 搜索二叉樹的搜索效率已經足夠高 , 僅僅log(N)的時間復雜度 .
- ? ? ? ? 但是 , 如果是按照近似有序序列來插入 , 會導致樹形結構退化為普通的鏈式結構 (沒錯,和普通單鏈表一樣了!!!).
- ? ? ? ? 于是 , 本節索要探討的AVL樹的結構就應運而生了(AVL是按照兩個前蘇聯科學家的名字來命名的,沒有特殊含義).
二,平衡因子:
? ? ? ? 想要避免搜索二叉樹的退化?, 就需要在恰當的時候通過旋轉來調整(之后的內容) , 而為了能夠更加優雅地理清旋轉的邏輯和簡化相關代碼編寫?, 平衡因子是我們可以提前準備的小工具 .
1`基本定義:
- 節點初始的平衡因子為0
- 在節點左邊插入新節點 , 平衡因子 +1
- 在節點右邊插入新節點,平衡因子 -1
- 形象的來說 , 一個節點的平衡因子 = 右子樹高度 - 左子樹高度 .
2`補充說明:
- ? ? ? ? 平衡因子可以讓我們以量化的方式來判斷一個節點左右子樹的高度差 , 以此來決定是否需要旋轉.
- ? ? ? ? ?對于AVL樹來說 , 任何一個節點的平衡因子的絕對值必須小于等于1 .
- ? ? ? ? ?之所以沒有要求節點的平衡因子嚴格等于0 , 是因為在部分情況下不現實 , 比如整棵樹只有兩個節點時 , 那要么左子樹高度低要么右子樹高度低 .
- ? ? ? ? ?下面列舉幾個例子來具象化展示:?




三,結構的設計:
? ? ?總的類框架沒變 , 還是分為一個主類來包含一個根節點的成員變量以及各種成員函數 , 和一個節點類 . 只是節點類的成員變量稍有擴充? ?
????????1`擴充的成員變量:?
AVL樹 | |
和搜索二叉樹相同的成員變量 | 值key / 左孩子left / 右孩子right |
新增的成員變量一 :? | 平衡因子BF (單詞 balance factor的縮寫) |
新增的成員變量二: | 父節點parent (方便檢索平衡因子) |
//節點類
template<class K , class V>
struct AvlNode
{//構造AvlNode(K key , V val){_k.first = key;_k.second = val;_parent = nullptr;_left = nullptr;_right = nullptr;}//成員變量pair<K, V> _k;AvlNode* _parent; //新增AvlNode* _left;AvlNode* _right;int _bf = 0; //新增
};
?? ? ? ? 2`小坑:?
? ? ? ? 對于一顆初始為空的AVL樹 , 成員變量(_root)應該默認初始化為nullptr , 否則難以應對向空樹中插入元素時的情況.
//AVL樹的類聲明
template<class K>
class AVLTree
{
public:using Node = AVLNode<K>;
private:Node* _root == nullptr; //c++11的語法,用于初始化列表的缺省值
};
四,元素的插入:
? ? ? ? AVL樹就是在搜索二叉樹的基礎上通過旋轉來維持平衡 , 而插入元素的函數里也包含了平衡因子的維護和旋轉的邏輯.
? ? ? ? ?1`插入元素 :
? ? ? ? AVL樹插入元素的邏輯和二叉搜索樹類似 , 只不過每個節點多出來的BF(平衡因子)和parent(父節點)要求略微的調整.
- 函數接受一個插入的值 key.
- 定義一個指向根節點的局部指針變量cur , 和一個備用的局部指針變量parent來保存cur每次更新之前的值(放標最后鏈接)
- 每次通過判斷cur節點的key和待插入的key的大小來決定往左子樹還是右子樹更新.
- 如果cur走到空,則找到插入位置,借助以保存的parent變量來連接新節點 ; 如果cur走到值和key相同的節點,說明已存在該值,不做處理直接結束.
特殊情況 : 如果起初的根節點為空(nullptr) , 則直接讓新節點作根,結束函數體.
//insert函數插入數據的部分
//-----------------------------------------------------------
//根為空的空樹情況
if (nullptr == _root)
{_root = new Node(key);return true;
}Node* parent = nullptr;
Node* cur = _root;
while (cur)
{if (key < cur->_key) //待插入的節點值小于當前節點,則往左子樹{parent = cur;cur = cur->_left}else if (key > cur->_key) //待插入的節點值大于當前節點,則往右子樹{parent = cur;cur = cur->_right;}else{return false;}
}
Node* newNode = new Node(key);
//判斷新節點應該連接到父節點的哪邊(通過值判斷!!!)
if (key < parent->_key)
{parent->_left = newNode;
}
else
{parent->_right = newNode;
}
newNode->_parent = parent;
?????????2`平衡因子的維護:
? ? ? ? 前面已經基本介紹過了節點平衡因子的定義 , 用代碼維護起來也比較簡單.
- 使用兩個局部指針變量 , 一個新插入節點的指針cur , 另一個是他的父節點指針parent
- 新節點cur的平衡因子是天然的0 , 所以從parent的平衡因子開始判斷.
- 若新節點cur是parent的左節點 , parent的平衡因子BS減一 ;? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?若新節點cur是parent的右節點 , parent的平衡因子BS加一;
- 接下來直接判斷更新后的當前parent的平衡因子 , 有三種情況:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 第一種 : parent更新后平衡因子為0 , 說明平衡了 , 不用旋轉 , 結束插入的過程.? ? ? ? ? ? ? 第二種 : parent更新后的平衡因子為 1或-1 , 說明還是平衡 , 但是需要將cur和parent全部往上更新一層 , 進行第?3 點的操作.? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 第三種 : parent更新后的平衡因子為2或-2 , 說明當前節點的左右子樹不平衡 , 需要旋轉(下文的內容,此處省略).
//維護平衡因子
cur = newNode;
while (parent)
{if (parent->_left == cur){parent->_bf--;}else{parent->_bf++;}if (parent->_bf == 0){return true;}else if (abs(parent->_bf) == 1){cur = parent;parent = parent->_parent;}else if (abs(parent->_bf) == 2){//旋轉的類型和邏輯 , 此處略}else{assert(false); //防御性編程}
}
? ? ? ? 3`旋轉:
? ? ? ? 旋轉是最有意思的部分 , 通過節點不平衡的情況來判斷旋轉的類型 .
????????旋轉的類型有左單旋/右單旋/右左雙旋/左右雙旋.
- 單旋轉 : 存粹的左子樹高或者右子樹高 , 可以大致理解為新插入的節點是左子樹的最左邊或右子樹的最右邊 .
- 雙旋轉:不存粹的左子樹高或者右子樹高 , 可以大致理解為新插入的節點是左子樹的葉子結點的右側 或 右子樹的葉子結點的左側.
不純粹的高 :
???????? 如果是看圖 , 會發現一個節點的第一個節點插入到了左邊(右邊) , 下一個節點插入在了剛剛插入的節點的右邊(右邊) ;
????????如果是看平衡因子 , 令最后插入的節點為cur , 則cur->Parent的平衡因子和cur->Parent->Parent的平衡因子異號.
????????1`右單旋:
// 右單旋
void RotateR(Node* root)
{Node* subL = root->_left;Node* subLR = subL->_right;//將左子樹的左子樹節點鏈接給旋轉節點的左邊root->_left = subLR;if (subLR != nullptr) //防止左子樹的左子樹的跟節點為空{subLR->_parent = root;}//提前保存節點旋轉之前的父節點Node* oldParent = root->_parent;//將旋轉節點和其左子節點顛倒父子關系subL->_right = root;root->_parent = subL;if (root == _root) //待旋轉節點為整棵樹的跟的情況{_root = subL;subL->_parent = nullptr;}else{//讓subL和root的父節點建立鏈接if (oldParent->_right == root){oldParent->_right = subL;}else{oldParent->_left = subL;}subL->_parent = oldParent;}//更新平衡因子root->_bf = 0;subL->_bf = 0;
}
????????2`左單旋:
//左單旋
void RotateL(Node* root)
{Node* subR = root->_right;Node* subRL = subR->_left;root->_right = subRL;if (subRL != nullptr){subRL->_parent = root;}Node* oldParent = root->_parent;subR->_left = root;root->_parent = subR;if (root == _root){_root = subR ;subR->_parent = nullptr;}else{if (oldParent->_left == root){oldParent->_left = subR;}else{oldParent->_right = subR;}subR->_parent = oldParent;}root->_bf = 0;subR->_bf = 0;
}
?????? ?3`左右雙旋:
?
//左右雙旋
void RotateLR(Node* root)
{Node* subL = root->_left;Node* subLR = subL->_right;int bf_subLR = subLR->_bf;RotateL(subL);RotateR(root);if (bf_subLR == 0){root->_bf = 0;subL->_bf = 0;subLR->_bf = 0;}else if (bf_subLR == 1){root->_bf = 0;subL->_bf = -1;subLR->_bf = 0;}else if (bf_subLR == -1){root->_bf = 1;subL->_bf = 0;subLR->_bf = 0;}else{assert(false);}
}
????????4`右左雙旋:
//右左雙旋
void RotateRL(Node* root)
{Node* subR = root->_right;Node* subRL = subR->_left;int bf_subRL = subRL->_bf;RotateR(subR);RotateL(root);if (bf_subRL == 0){root->_bf = 0;subR->_bf = 0;subRL->_bf = 0;}else if(bf_subRL == 1){root->_bf = -1;subR->_bf = 0;subRL->_bf = 0;}else if (bf_subRL == -1){root->_bf = 1;subR->_bf = 0;subRL->_bf = 0;}else{assert(false);}
}
五`元素的查找(按值):
? ? ? ? 元素的查找平平無奇 , 和搜索二叉樹一個樣 ,? 都是拿著待查找的值key從根節點開始比對 , key更小則找左子樹,更大則找右子樹 .
//按值查找節點
Node* Find(const K& key)
{Node* cur = _root;while (cur){if (key < cur->_key){cur = cur->_left;}else if (key > cur->_key){cur = cur->_right;}else{return cur;}}return false;
}
?六`中序遍歷順序輸出:
? ? ? ? 一般來說 , AVL樹的涉及使得? 左子樹的值<根節點的值<右子樹的值 . 所以通過中序遍歷(左子樹->根節點->左子樹)正好就可以得到一個順序序列.
? ? ? ? 需要注意的是:樹形結構的遞歸操作需要將根節點作為參數傳遞,但由于根節點通常作為類的私有成員變量,無法在外部直接訪問。因此,在C++中常見的做法是提供一個公有成員函數來傳遞根節點,從而確保內部的遞歸函數能夠正常運行。
//設置為共有成員函數,方便外部調用
public:
void MidTrave()
{_MidTrave(_root);cout << endl;
}//設置成私有成員函數,僅僅讓類類內部訪問
private:
void _MidTrave(Node* root)
{if (nullptr == root){return;}_MidTrave(root->_left);cout << root->_key << " ";_MidTrave(root->_right);
}
七`類的結構和成員函數代碼匯總:
....//頭文件的包含此處省略
----------------------------
//節點類的聲明
template<class K>
struct AVLNode
{AVLNode(K key):_key(key), _parent(nullptr), _left(nullptr), _right(nullptr), _bf(0){}K _key;AVLNode<K>* _parent;AVLNode<K>* _left;AVLNode<K>* _right;int _bf;
};//AVL樹的類聲明
template<class K>
class AVLTree
{
public:using Node = AVLNode<K>;//插入bool Insert(const K& key){//根為空的空樹情況if (nullptr == _root){_root = new Node(key);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (key < cur->_key) //待插入的節點值小于當前節點,則往左子樹{parent = cur;cur = cur->_left;}else if (key > cur->_key) //待插入的節點值大于當前節點,則往右子樹{parent = cur;cur = cur->_right;}else{return false;}}Node* newNode = new Node(key);//判斷新節點應該連接到父節點的哪邊(通過值判斷!!!)if (key < parent->_key){parent->_left = newNode;}else{parent->_right = newNode;}newNode->_parent = parent;//----------------------------------------------------------------//維護平衡因子cur = newNode;while (parent){if (parent->_left == cur){parent->_bf--;}else{parent->_bf++;}if (parent->_bf == 0){return true;}else if (abs(parent->_bf) == 1){cur = parent;parent = parent->_parent;}else if (abs(parent->_bf) == 2){if (parent->_bf == -2 && cur->_bf <= 0) //右單旋{RotateR(parent);}else if (parent->_bf == 2 && cur->_bf >= 0) //左單旋{RotateL(parent);}else if (parent->_bf == -2 && cur->_bf >= 0) //左右雙旋{RotateLR(parent);}else if (parent->_bf == 2 && cur->_bf <= 0) //右左雙旋{RotateRL(parent);}else{assert(false);}break;}else{assert(false);}}return true;}// 右單旋void RotateR(Node* root) {Node* subL = root->_left;Node* subLR = subL->_right;//將左子樹的左子樹節點鏈接給旋轉節點的左邊root->_left = subLR;if (subLR != nullptr) //防止左子樹的左子樹的跟節點為空{subLR->_parent = root;}//提前保存節點旋轉之前的父節點Node* oldParent = root->_parent;//將旋轉節點和其左子節點顛倒父子關系subL->_right = root;root->_parent = subL;if (root == _root) //待旋轉節點為整棵樹的跟的情況{_root = subL;subL->_parent = nullptr;}else{//讓subL和root的父節點建立鏈接if (oldParent->_right == root){oldParent->_right = subL;}else{oldParent->_left = subL;}subL->_parent = oldParent;}//更新平衡因子root->_bf = 0;subL->_bf = 0;}//左單旋void RotateL(Node* root){Node* subR = root->_right;Node* subRL = subR->_left;root->_right = subRL;if (subRL != nullptr){subRL->_parent = root;}Node* oldParent = root->_parent;subR->_left = root;root->_parent = subR;if (root == _root){_root = subR ;subR->_parent = nullptr;}else{if (oldParent->_left == root){oldParent->_left = subR;}else{oldParent->_right = subR;}subR->_parent = oldParent;}root->_bf = 0;subR->_bf = 0;}//左右雙旋void RotateLR(Node* root){Node* subL = root->_left;Node* subLR = subL->_right;int bf_subLR = subLR->_bf;RotateL(subL);RotateR(root);if (bf_subLR == 0){root->_bf = 0;subL->_bf = 0;subLR->_bf = 0;}else if (bf_subLR == 1){root->_bf = 0;subL->_bf = -1;subLR->_bf = 0;}else if (bf_subLR == -1){root->_bf = 1;subL->_bf = 0;subLR->_bf = 0;}else{assert(false);}}//右左雙旋void RotateRL(Node* root){Node* subR = root->_right;Node* subRL = subR->_left;int bf_subRL = subRL->_bf;RotateR(subR);RotateL(root);if (bf_subRL == 0){root->_bf = 0;subR->_bf = 0;subRL->_bf = 0;}else if(bf_subRL == 1){root->_bf = -1;subR->_bf = 0;subRL->_bf = 0;}else if (bf_subRL == -1){root->_bf = 1;subR->_bf = 0;subRL->_bf = 0;}else{assert(false);}}//按值查找節點Node* Find(const K& key){Node* cur = _root;while (cur){if (key < cur->_key){cur = cur->_left;}else if (key > cur->_key){cur = cur->_right;}else{return cur;}}return nullptr;}void MidTrave(){_MidTrave(_root);cout << endl;}
private:void _MidTrave(Node* root){if (nullptr == root){return;}_MidTrave(root->_left);cout << root->_key << " ";_MidTrave(root->_right);}Node* _root = nullptr;
};