C++ AVLTree高度平衡的二叉搜索樹(shù)深入分析
一、AVL樹(shù)的概念
二叉搜索樹(shù)雖可以縮短查找的效率,但如果數(shù)據(jù)有序或接近有序二叉搜索樹(shù)將退化為單支樹(shù),查找元素相當(dāng)于在順序表中搜索元素,效率低下。
因此,兩位俄羅斯的數(shù)學(xué)家G.M.Adelson-Velskii和E.M.Landis在1962年發(fā)明了一種解決上述問(wèn)題的方法:當(dāng)向二叉搜索樹(shù)中插入新結(jié)點(diǎn)后,如果能保證每個(gè)結(jié)點(diǎn)的左右子樹(shù)高度之差的絕對(duì)值不超過(guò)1(需要對(duì)樹(shù)中的結(jié)點(diǎn)進(jìn)行調(diào)整),即可降低樹(shù)的高度,從而減少平均搜索長(zhǎng)度。
一棵AVL樹(shù)或者是空樹(shù),或者是具有以下性質(zhì)的二叉搜索樹(shù):
它的左右子樹(shù)都是AVL樹(shù)
左右子樹(shù)高度之差(簡(jiǎn)稱平衡因子)的絕對(duì)值不超過(guò)1(-1/0/1)
平衡因子= 右子樹(shù)高度-左子樹(shù)高度
如果一棵二叉搜索樹(shù)是高度平衡的,它就是AVL樹(shù)。如果它有n個(gè)結(jié)點(diǎn),其高度可保持在O(log2N) ,搜索時(shí)間復(fù)雜度O(log2N)
二、AVL樹(shù)節(jié)點(diǎn)的定義
節(jié)點(diǎn)結(jié)構(gòu):三叉鏈結(jié)構(gòu)(左、右、父),以及平衡因子bf+構(gòu)造函數(shù)(左右為空,平衡因子初始化為0)
template<class K,class V> struct AVLTreeNode { pair<K, V> _kv; AVLTreeNode<K, V>* _left; AVLTreeNode<K, V>* _right; AVLTreeNode<K, V>* _parent; int _bf;//balance factor AVLTreeNode(const pair<K,V>&kv) :_kv(kv) ,_left(nullptr) ,_right(nullptr) ,_parent(nullptr) ,_bf(0) {} };
三、AVL樹(shù)的插入
AVL樹(shù)在二叉搜索樹(shù)的基礎(chǔ)上引入了平衡因子,因此AVL樹(shù)也可以看成是二叉搜索樹(shù)。步驟過(guò)程:
找到插入的位置:根據(jù)二叉搜索樹(shù)的做法
進(jìn)行插入:判斷插入的位置是parent的左還是右
更新平衡因子:如果不平衡的話,就要進(jìn)行旋轉(zhuǎn)
找到插入位置(比較節(jié)點(diǎn)大小即可):
- 插入的節(jié)點(diǎn)key值
>
當(dāng)前位置的key值,往右子樹(shù)走 - 插入的節(jié)點(diǎn)key值
<
當(dāng)前位置的key值,往左子樹(shù)走 - 插入的節(jié)點(diǎn)key值等于當(dāng)前位置的key值,不能插入,返回false
插入之后,與二叉搜索樹(shù)不同的是:我們還需要去進(jìn)行平衡因子的更新,調(diào)平衡:
如果新增加的在右,平衡因子加加
如果新增加的在左,平衡因子減減
更新一個(gè)結(jié)點(diǎn)之后我們需要去進(jìn)行判斷,子樹(shù)的高度是否發(fā)生了變化:
1.如果parent的平衡因子是0:說(shuō)明之前parent的平衡因子是1或-1,說(shuō)明之前parent一邊高、一邊低;這次插入之后填入矮的那邊,parent所在的子樹(shù)高度不變,不需要繼續(xù)往上更新
2.如果parent的平衡因子是1或者-1:說(shuō)明之前parent的平衡因子是0,兩邊一樣高,插入之后一邊更高,parent所在的子樹(shù)高度發(fā)生變化,繼續(xù)往上更新
3.平衡因子是2或-2,說(shuō)明之前parent的平衡因子是1或-1,現(xiàn)在插入嚴(yán)重不平衡,違反規(guī)則,需要進(jìn)行旋轉(zhuǎn)處理
最壞的情況下:需要一直更新到根root:
我們更新平衡因子時(shí)第一個(gè)更新的就是parent,如果parent->_bf1或parent->_bf-1需要繼續(xù)往上進(jìn)行平衡因子的更新,向上迭代,直到parent為空的情況:
else if (parent->_bf == 1 || parent->_bf == -1) { cur = parent; parent = parent->_parent; }
當(dāng)parent->_bf = 2或parent->_bf==-2時(shí),我們就需要進(jìn)行旋轉(zhuǎn)了:
??如果parent的平衡因子是2,cur的平衡因子是1時(shí),說(shuō)明右邊的右邊比較高,我們需要進(jìn)行左單旋
??如果parent的平衡因子是-2,cur的平衡因子是-1時(shí),說(shuō)明左邊的左邊比較高,我們需要進(jìn)行右單旋
??如果parent的平衡因子是-2,cur的平衡因子是1時(shí),我們需要進(jìn)行左右雙旋
??如果parent的平衡因子是2,cur的平衡因子是-1時(shí),我們需要進(jìn)行右左雙旋
bool Insert(const pair<K, V>& kv) { if (_root == nullptr) { _root = new Node(kv); return true; } Node* parent = nullptr; Node* cur = _root; while (cur) { if (cur->_kv.first < kv.first) { parent = cur; cur = cur->_right; } else if (cur->_kv.first > kv.first) { parent = cur; cur = cur->_left; } else { return false; } } cur = new Node(kv); if (parent->_kv.first < kv.first) { parent->_right = cur; cur->_parent = parent; } else { parent->_left = cur; cur->_parent = parent; } //更新平衡因子 while (parent) { if (cur == parent->_left) { parent->_bf--; } else { parent->_bf++; } if (parent->_bf == 0) { break; } else if (parent->_bf == 1 || parent->_bf == -1) { cur = parent; parent = parent->_parent; } else if(parent->_bf==2||parent->_bf==-2) { //左旋轉(zhuǎn) if (parent->_bf == 2 && cur->_bf == 1) { RotateL(parent); } //右旋 else if (parent->_bf == -2 && cur->_bf == -1) { RotateR(parent); } //左右雙旋 else if (parent-> _bf == -2&&cur->_bf==1) { RotateLR(parent); } //右左雙旋 else if (parent->_bf ==2&&cur->_bf ==-1) { RotateRL(parent); } else { assert(false); } break; } else { assert(false); } } return true; }
四、AVL樹(shù)的旋轉(zhuǎn)
在一棵原本是平衡的AVL樹(shù)中插入一個(gè)新節(jié)點(diǎn),可能造成不平衡,此時(shí)必須調(diào)整樹(shù)的結(jié)構(gòu),使之平衡化。根據(jù)節(jié)點(diǎn)插入位置的不同,AVL樹(shù)的旋轉(zhuǎn)分為四種。
旋轉(zhuǎn)規(guī)則:
1.讓這顆子樹(shù)左右高度差不超過(guò)1
2.旋轉(zhuǎn)過(guò)程中繼續(xù)保持它是搜索樹(shù)
3.更新調(diào)整孩子節(jié)點(diǎn)的平衡因子
4.讓這顆子樹(shù)的高度根插入前保持一致
1.左單旋
新節(jié)點(diǎn)插入較高右子樹(shù)的右側(cè)—右右:左單旋
抽象圖:
a/b/c是高度為h的AVL子樹(shù),代表多數(shù)情況:h>=0,其中h可以等于0、1、2…,不過(guò)都可以抽象成h,處理情況都一樣:此時(shí)parent等于2,subR等于1。
具體左旋的步驟:
subRL成為parent的右子樹(shù):注意subL和parent
的關(guān)系,調(diào)整parent的右以及subRL的父(subRL可能為空)
parent成為subR的左子樹(shù):調(diào)整parent的父與subR的左
subR成為相對(duì)的根節(jié)點(diǎn):調(diào)整subR與ppNode:注意parent是不是整棵樹(shù)的root,如果是,則讓subR為_(kāi)root,同時(shí)讓_root->_parent置為空
更新平衡因子
左旋調(diào)整:subR的左子樹(shù)值(subRL)本身就比parent的值要大,所以可以作為parent的右子樹(shù);而parent及其左子樹(shù)當(dāng)中結(jié)點(diǎn)的值本身就比subR的值小,所以可以作為subR的左子樹(shù)。
**更新平衡因子bf:**subR與parent的bf都更新為0
代碼實(shí)現(xiàn)左旋轉(zhuǎn):
//左單旋 void RotateL(Node* parent) { Node* subR = parent->_right; Node* subRL = subR->_left; parent->_right = subRL; if (subRL) subRL->_parent = parent; Node* ppNode = parent->_parent; subR->_left = parent; parent->_parent = subR; if (ppNode == nullptr) { _root = subR; _root->_parent = nullptr; } else { if (ppNode->_left == parent) { ppNode->_left = subR; } else { ppNode->_right = subR; } subR->_parent = ppNode; } parent->_bf = subR->_bf = 0; }
2.右單旋
新節(jié)點(diǎn)插入較高左子樹(shù)的左側(cè)—左左:右單旋
有了前面左旋的基礎(chǔ),我們?cè)趤?lái)看右旋就沒(méi)有那么費(fèi)勁了:
a/b/c是高度為h的AVL樹(shù),右旋旋轉(zhuǎn)動(dòng)作:b變成60的左邊,60變成30的右邊,30變成子樹(shù)的根。
30比60小,b值是處于30和60之間,此時(shí)作為60的左邊是沒(méi)有問(wèn)題的。
有了這個(gè)圖,在結(jié)合前面左單旋的基礎(chǔ),我們就能很快實(shí)現(xiàn)我們的右單旋代碼:
//右單旋 void RotateR(Node* parent) { Node* subL = parent->_left; Node* subLR = subL->_right; parent->_left = subLR; if (subLR) subLR->_parent = parent; Node* ppNode = parent->_parent; parent->_parent = subL; subL->_right = parent; //if(_root==parent) if (ppNode == nullptr) { _root = subL; _root->_parent = nullptr; } else { if (ppNode->_left == parent) { ppNode->_left = subL; } else { ppNode->_right = subL; } subL->_parent = ppNode; } subL->_bf = parent->_bf = 0; }
3.左右雙旋
新節(jié)點(diǎn)插入較高左子樹(shù)的右側(cè)—左右:先左單旋再右單旋
a/d是高度為h的AVL樹(shù),b/c是高度為h-1的AVL樹(shù)。
以30為軸點(diǎn)進(jìn)行左單旋:b變成30的右邊,30變成60的左邊,60變成子樹(shù)根
以90為軸點(diǎn)進(jìn)行右單旋:c變成90的左邊,90變成60的右邊,60變成子樹(shù)的根
左右雙旋:以subL為軸點(diǎn)左旋,以parent為軸點(diǎn)進(jìn)行右旋,在進(jìn)行平衡因子的更新(最大的問(wèn)題)
我們從總體的角度來(lái)看,左右雙旋的結(jié)果就是:就是把subLR的左子樹(shù)和右子樹(shù),分別作為subL和parent的右子樹(shù)和左子樹(shù),同時(shí)subL和parent分別作為subLR的左右子樹(shù),最后讓subLR作為整個(gè)子樹(shù)的根
subLR的左子樹(shù)作為subL的右子樹(shù):因?yàn)閟ubLR的左子樹(shù)結(jié)點(diǎn)比subL的大
subLR的右子樹(shù)作為parent的左子樹(shù):因?yàn)閟ubLR的右子樹(shù)結(jié)點(diǎn)比parent的小
平衡因子的更新:重新判斷(識(shí)別插入節(jié)點(diǎn)是在b還是在c)根據(jù)subLR平衡因子的初始情況進(jìn)行分類(lèi):
如果subLR初始平衡因子是-1時(shí),左右雙旋后parent、subL、subLR的平衡因子分別更新為1、0、0(插入在b)
如果subLR的初始平衡因子是1時(shí),左右雙旋后parent、subL、subLR的平衡因子分別更新為0、-1、0(插入在c)
如果subLR初始平衡因子是0時(shí),左右雙旋后parent、subL、subLR的平衡因子分別更新為0、0、0(subLR自己新增)
代碼實(shí)現(xiàn):
//左右雙旋 void RotateLR(Node* parent) { Node* subL = parent->_left; Node* subLR = subL->_right; int bf = subLR ->_bf; RotateL(parent->_left); RotateR(parent); //更新平衡因子 if (bf == -1)//b插入,subLR左子樹(shù)新增 { subL->_bf = 0; parent->_bf = 1; subLR->_bf = 0; } else if (bf == 1)//c插入,subLR右子樹(shù)新增 { parent->_bf = 0; subL->_bf = -1; subLR->_bf = 0; } else if (bf == 0)//subLR自己新增加 { parent->_bf = 0; subL->_bf = 0; subLR->_bf = 0; } else { assert(false); } }
4.右左雙旋
新節(jié)點(diǎn)插入較高右子樹(shù)的左側(cè)—右左:先右單旋再左單旋
插入
subR為軸點(diǎn)進(jìn)行右單旋:
parent為軸進(jìn)行左單旋:
既右左雙旋:
右左雙旋后,根據(jù)subRL 初始平衡因子的不同分為三種情況分別對(duì)應(yīng)subRL
= 0、1、-1情況,與左右雙旋情況類(lèi)似。
void RotateRL(Node* parent) { Node* subR = parent->_right; Node* subRL = subR->_left; int bf = subRL->_bf; RotateR(subR); RotateL(parent); if (bf == 1) { subR->_bf = 0; subRL->_bf = 0; parent->_bf = -1; } else if (bf == -1) { subR->_bf = 1; subRL->_bf = 0; parent->_bf = 0; } else if (bf == 0) { subR->_bf = 0; subRL->_bf = 0; parent->_bf = 0; } else { assert(false); } }
五、進(jìn)行驗(yàn)證
AVL樹(shù)是在二叉搜索樹(shù)的基礎(chǔ)上加入了平衡性的限制,因此要驗(yàn)證AVL樹(shù),可以分兩步:
驗(yàn)證其為二叉搜索樹(shù)
如果中序遍歷可得到一個(gè)有序的序列,就說(shuō)明為二叉搜索樹(shù)
void _InOrder(Node* root) { if (root == nullptr) return; _InOrder(root->_left); cout << root->_kv.first << ":" << root->_kv.second << endl; _InOrder(root->_right); }
驗(yàn)證其為平衡樹(shù)
每個(gè)節(jié)點(diǎn)子樹(shù)高度差的絕對(duì)值不超過(guò)1(注意節(jié)點(diǎn)中如果沒(méi)有平衡因子)節(jié)點(diǎn)的平衡因子是否計(jì)算正確
如果是空樹(shù),是AVL樹(shù);高度差不大于2,并且遞歸左右子樹(shù)的高度差都不大于2,也是AVL樹(shù);判斷平衡因子和該點(diǎn)的高度差是否相等
//求高度 int Height(Node* root) { if (root == nullptr) return 0; int lh = Height(root->_left); int rh = Height(root->_right); return lh > rh ? lh + 1 : rh + 1; } //判斷平衡 bool IsBalance(Node* root) { if (root == nullptr) { return true; } int leftHeight = Height(root->_left); int rightHeight = Height(root->_right); if (rightHeight - leftHeight != root->_bf) { cout << root->_kv.first << "平衡因子異常" << endl; return false; } return abs(rightHeight - leftHeight) < 2 && IsBalance(root->_left) && IsBalance(root->_right); }
六、AVLTree的性能
AVL樹(shù)是一棵絕對(duì)平衡的二叉搜索樹(shù),其要求每個(gè)節(jié)點(diǎn)的左右子樹(shù)高度差的絕對(duì)值都不超過(guò)1,這樣可以保證查詢時(shí)高效的時(shí)間復(fù)雜度即log2( N) 。但是如果要對(duì)AVL樹(shù)做一些結(jié)構(gòu)修改的操作,性能非常低下,比如:插入時(shí)要維護(hù)其絕對(duì)平衡,旋轉(zhuǎn)的次數(shù)比較多,更差的是在刪除時(shí),有可能一直要讓旋轉(zhuǎn)持續(xù)到根的位置。
因此:如果需要一種查詢高效且有序的數(shù)據(jù)結(jié)構(gòu),而且數(shù)據(jù)的個(gè)數(shù)為靜態(tài)的(即不會(huì)改變),可以考慮AVL樹(shù),但一個(gè)結(jié)構(gòu)經(jīng)常修改,就不太適合.
送上源碼:
#pragma once #include <iostream> #include <assert.h> #include <time.h> using namespace std; template<class K,class V> struct AVLTreeNode { pair<K, V> _kv; AVLTreeNode<K, V>* _left; AVLTreeNode<K, V>* _right; AVLTreeNode<K, V>* _parent; int _bf;//balance factor AVLTreeNode(const pair<K,V>&kv) :_kv(kv) ,_left(nullptr) ,_right(nullptr) ,_parent(nullptr) ,_bf(0) {} }; template <class K,class V> struct AVLTree { typedef AVLTreeNode<K, V> Node; public: bool Insert(const pair<K, V>& kv) { if (_root == nullptr) { _root = new Node(kv); return true; } Node* parent = nullptr; Node* cur = _root; while (cur) { if (cur->_kv.first < kv.first) { parent = cur; cur = cur->_right; } else if (cur->_kv.first > kv.first) { parent = cur; cur = cur->_left; } else { return false; } } cur = new Node(kv); if (parent->_kv.first < kv.first) { parent->_right = cur; cur->_parent = parent; } else { parent->_left = cur; cur->_parent = parent; } //更新平衡因子 while (parent) { if (cur == parent->_left) { parent->_bf--; } else { parent->_bf++; } if (parent->_bf == 0) { break; } else if (parent->_bf == 1 || parent->_bf == -1) { cur = parent; parent = parent->_parent; } else if(parent->_bf==2||parent->_bf==-2) { //左旋轉(zhuǎn) if (parent->_bf == 2 && cur->_bf == 1) { RotateL(parent); } //右旋 else if (parent->_bf == -2 && cur->_bf == -1) { RotateR(parent); } //左右雙旋 else if (parent-> _bf == -2&&cur->_bf==1) { RotateLR(parent); } //右左雙旋 else if (parent->_bf ==2&&cur->_bf ==-1) { RotateRL(parent); } else { assert(false); } break; } else { assert(false); } } return true; } //左單旋 void RotateL(Node* parent) { Node* subR = parent->_right; Node* subRL = subR->_left; parent->_right = subRL; if (subRL) subRL->_parent = parent; Node* ppNode = parent->_parent; subR->_left = parent; parent->_parent = subR; if (ppNode == nullptr) { _root = subR; _root->_parent = nullptr; } else { if (ppNode->_left == parent) { ppNode->_left = subR; } else { ppNode->_right = subR; } subR->_parent = ppNode; } parent->_bf = subR->_bf = 0; } void RotateR(Node* parent) { Node* subL = parent->_left; Node* subLR = subL->_right; parent->_left = subLR; if (subLR) subLR->_parent = parent; Node* ppNode = parent->_parent; parent->_parent = subL; subL->_right = parent; //if(_root==parent) if (ppNode == nullptr) { _root = subL; _root->_parent = nullptr; } else { if (ppNode->_left == parent) { ppNode->_left = subL; } else { ppNode->_right = subL; } subL->_parent = ppNode; } subL->_bf = parent->_bf = 0; } //左右雙旋 void RotateLR(Node* parent) { Node* subL = parent->_left; Node* subLR = subL->_right; int bf = subLR ->_bf; RotateL(parent->_left); RotateR(parent); //更新平衡因子 if (bf == -1)//b插入,subLR左子樹(shù)新增 { subL->_bf = 0; parent->_bf = 1; subLR->_bf = 0; } else if (bf == 1)//c插入,subLR右子樹(shù)新增 { parent->_bf = 0; subL->_bf = -1; subLR->_bf = 0; } else if (bf == 0)//subLR自己新增加 { parent->_bf = 0; subL->_bf = 0; subLR->_bf = 0; } else { assert(false); } } //右左雙旋 void RotateRL(Node* parent) { Node* subR = parent->_right; Node* subRL = subR->_left; int bf = subRL->_bf; RotateR(subR); RotateL(parent); if (bf == 1) { subR->_bf = 0; subRL->_bf = 0; parent->_bf = -1; } else if (bf == -1) { subR->_bf = 1; subRL->_bf = 0; parent->_bf = 0; } else if (bf == 0) { subR->_bf = 0; subRL->_bf = 0; parent->_bf = 0; } else { assert(false); } } void InOrder() { _InOrder(_root); } void _InOrder(Node* root) { if (root == nullptr) return; _InOrder(root->_left); cout << root->_kv.first << ":" << root->_kv.second << endl; _InOrder(root->_right); } int Height(Node* root) { if (root == nullptr) return 0; int lh = Height(root->_left); int rh = Height(root->_right); return lh > rh ? lh + 1 : rh + 1; } bool IsBalance() { return IsBalance(_root); } bool IsBalance(Node* root) { if (root == nullptr) { return true; } int leftHeight = Height(root->_left); int rightHeight = Height(root->_right); if (rightHeight - leftHeight != root->_bf) { cout << root->_kv.first << "平衡因子異常" << endl; return false; } return abs(rightHeight - leftHeight) < 2 && IsBalance(root->_left) && IsBalance(root->_right); } private: Node* _root = nullptr; }; //測(cè)試 void TestAVLTree() { //int a[] = { 8,3,1,10,6,4,7,14,13 }; //int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 }; int a[] = { 4,2,6,1,3,5,15,7,16,14 }; AVLTree<int, int> t; for (auto e : a) { t.Insert(make_pair(e,e)); } t.InOrder(); cout << t.IsBalance() << endl; } void TestAVLTree2() { srand(time(0)); const size_t N = 100000; AVLTree<int, int> t; for (size_t i = 0; i < N; i++) { size_t x = rand(); t.Insert(make_pair(x, x)); } //t.InOrder(); cout << t.IsBalance() << endl; }
到此這篇關(guān)于C++ AVLTree高度平衡的二叉搜索樹(shù)深入分析的文章就介紹到這了,更多相關(guān)C++ AVLTree二叉搜索樹(shù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vscode 配置 C/C++編譯環(huán)境(完整教程)
這篇文章主要介紹了vscode 配置 C/C++編譯環(huán)境(完整教程),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09c語(yǔ)言統(tǒng)計(jì)素?cái)?shù)之和的實(shí)例
這篇文章主要介紹了c語(yǔ)言統(tǒng)計(jì)素?cái)?shù)之和的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12C語(yǔ)言數(shù)據(jù)結(jié)構(gòu)算法之實(shí)現(xiàn)快速傅立葉變換
這篇文章主要介紹了C語(yǔ)言數(shù)據(jù)結(jié)構(gòu)算法之實(shí)現(xiàn)快速傅立葉變換的相關(guān)資料,需要的朋友可以參考下2017-06-06C語(yǔ)言實(shí)現(xiàn)與電腦玩剪刀石頭布游戲
這篇文章主要為大家詳細(xì)介紹了如何通過(guò)C語(yǔ)言實(shí)現(xiàn)和電腦玩剪刀石頭布游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-11-11Qt中QList與QLinkedList類(lèi)的常用方法總結(jié)
這篇文章主要為大家詳細(xì)介紹了Qt中QList與QLinkedList類(lèi)的常用方法,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Qt有一定的幫助,需要的可以參考一下2022-12-12