目錄
1 -> 底層結構
2 -> AVL樹
2.1 -> AVL樹的概念
2.2 -> AVL樹節點的定義
2.3 -> AVL樹的插入
2.4 -> AVL樹的旋轉
2.5 -> AVL樹的驗證
2.6 -> AVL樹的性能
1 -> 底層結構
在上文中對對map/multimap/set/multiset進行了簡單的介紹,在其文檔介紹中發現,這幾個容器有個共同點是:其底層都是按照二叉搜索樹來實現的,但是二叉搜索樹有其自身的缺陷,假如往樹中
插入的元素有序或者接近有序,二叉搜索樹就會退化成單支樹,時間復雜度會退化成O(N),因此
map、set等關聯式容器的底層結構是對二叉樹進行了平衡處理,即采用平衡樹來實現。
2 -> AVL樹
2.1 -> AVL樹的概念
二叉搜索樹雖然可以縮短查找的效率,但如果數據有序或者接近有序的二叉搜索樹將退化成單支樹,查找元素相當于在順序表中搜索元素,效率低下。因此,兩位俄羅斯的數學家G.M.Adelson-Velskii和E.M.Landis在1962年發明了一種解決上述問題的方法:當向二叉搜索樹中插入新節點后,如果能保證每個節點的左右子樹的高度差的絕對值不超過1(需要對樹中的節點進行調整),即可降低樹的高度,從而減少平均搜索的長度。
一棵AVL樹或者空樹,或者是具有以下性質的二叉搜索樹:
- 它的左右子樹都是AVL樹
- 左右子樹高度之差(簡稱平衡因子)的絕對值不超過1(-1/0/1)
如果一棵二叉搜索樹是高度平衡的,它就是AVL樹。如果它有n個節點,其高度可保持在O(n),搜索時間復雜度O(n)。
2.2 -> AVL樹節點的定義
AVL樹節點的定義:
#define _CRT_SECURE_NO_WARNINGS 1#include <iostream>
using namespace std;template<class T>
struct AVLTreeNode
{AVLTreeNode(const T& data): _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr), _data(data), _bf(0){}AVLTreeNode<T>* _pLeft; // 該節點的左孩子AVLTreeNode<T>* _pRight; // 該節點的右孩子AVLTreeNode<T>* _pParent; // 該節點的雙親T _data;int _bf; // 該節點的平衡因子
};
2.3 -> AVL樹的插入
AVL樹就是在二叉搜索樹的基礎上引入了平衡因子,因此AVL樹也可以看成是二叉搜索樹。那么AVL樹的插入過程可以分為兩步:
- 按照二叉搜索樹的方式插入新節點。
- 調整節點的平衡因子。
#define _CRT_SECURE_NO_WARNINGS 1#include <iostream>
using namespace std;template<class T>
struct AVLTreeNode
{AVLTreeNode(const T& data): _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr), _data(data), _bf(0){}AVLTreeNode<T>* _pLeft; // 該節點的左孩子AVLTreeNode<T>* _pRight; // 該節點的右孩子AVLTreeNode<T>* _pParent; // 該節點的雙親T _data;int _bf; // 該節點的平衡因子bool Insert(const T& data){// 1. 先按照二叉搜索樹的規則將節點插入到AVL樹中// 2. 新節點插入后,AVL樹的平衡性可能會遭到破壞,// 此時就需要更新平衡因子,并檢測是否破壞了AVL樹的平衡性/*pCur插入后,pParent的平衡因子一定需要調整,在插入之前,pParent的平衡因子分為三種情況:-1,0, 1, 分以下兩種情況:1. 如果pCur插入到pParent的左側,只需給pParent的平衡因子-1即可2. 如果pCur插入到pParent的右側,只需給pParent的平衡因子+1即可此時:pParent的平衡因子可能有三種情況:0,正負1, 正負21. 如果pParent的平衡因子為0,說明插入之前pParent的平衡因子為正負1,插入后被調整成0,此時滿足AVL樹的性質,插入成功2. 如果pParent的平衡因子為正負1,說明插入前pParent的平衡因子一定為0,插入后被更新成正負1,此時以pParent為根的樹的高度增加,需要繼續向上更新3. 如果pParent的平衡因子為正負2,則pParent的平衡因子違反平衡樹的性質,需要對其進行旋轉處理*/while (pParent){// 更新雙親的平衡因子if (pCur == pParent->_pLeft)pParent->_bf--;elsepParent->_bf++;// 更新后檢測雙親的平衡因子if (0 == pParent->_bf){break;}else if (1 == pParent->_bf || -1 == pParent->_bf){// 插入前雙親的平衡因子是0,插入后雙親的平衡因為為1 或者 -1 ,說明以雙親為根的二叉樹// 的高度增加了一層,因此需要繼續向上調整pCur = pParent;pParent = pCur->_pParent;}else{// 雙親的平衡因子為正負2,違反了AVL樹的平衡性,需要對以pParent// 為根的樹進行旋轉處理if (2 == pParent->_bf){// ...}else{// ...}}}return true;}};
2.4 -> AVL樹的旋轉
如果在一棵原本是平衡的AVL樹中插入一個新節點,可能造成不平衡,此時必須調整樹的結構,使之平衡化。根據節點插入位置的不同,AVL樹的旋轉分為四種:
1. 新節點插入較高左子樹的左側——左左:右單旋
#define _CRT_SECURE_NO_WARNINGS 1#include <iostream>
using namespace std;template<class T>
struct AVLTreeNode
{AVLTreeNode(const T& data): _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr), _data(data), _bf(0){}AVLTreeNode<T>* _pLeft; // 該節點的左孩子AVLTreeNode<T>* _pRight; // 該節點的右孩子AVLTreeNode<T>* _pParent; // 該節點的雙親T _data;int _bf; // 該節點的平衡因子bool Insert(const T& data){// 1. 先按照二叉搜索樹的規則將節點插入到AVL樹中// 2. 新節點插入后,AVL樹的平衡性可能會遭到破壞,// 此時就需要更新平衡因子,并檢測是否破壞了AVL樹的平衡性/*pCur插入后,pParent的平衡因子一定需要調整,在插入之前,pParent的平衡因子分為三種情況:-1,0, 1, 分以下兩種情況:1. 如果pCur插入到pParent的左側,只需給pParent的平衡因子-1即可2. 如果pCur插入到pParent的右側,只需給pParent的平衡因子+1即可此時:pParent的平衡因子可能有三種情況:0,正負1, 正負21. 如果pParent的平衡因子為0,說明插入之前pParent的平衡因子為正負1,插入后被調整成0,此時滿足AVL樹的性質,插入成功2. 如果pParent的平衡因子為正負1,說明插入前pParent的平衡因子一定為0,插入后被更新成正負1,此時以pParent為根的樹的高度增加,需要繼續向上更新3. 如果pParent的平衡因子為正負2,則pParent的平衡因子違反平衡樹的性質,需要對其進行旋轉處理*/while (pParent){// 更新雙親的平衡因子if (pCur == pParent->_pLeft)pParent->_bf--;elsepParent->_bf++;// 更新后檢測雙親的平衡因子if (0 == pParent->_bf){break;}else if (1 == pParent->_bf || -1 == pParent->_bf){// 插入前雙親的平衡因子是0,插入后雙親的平衡因為為1 或者 -1 ,說明以雙親為根的二叉樹// 的高度增加了一層,因此需要繼續向上調整pCur = pParent;pParent = pCur->_pParent;}else{// 雙親的平衡因子為正負2,違反了AVL樹的平衡性,需要對以pParent// 為根的樹進行旋轉處理if (2 == pParent->_bf){// ...}else{// ...}}}return true;}/*在插入前,AVL樹是平衡的,新節點插入到30的左子樹(注意:此處不是左孩子)中,30左子樹增加了一層,導致以60為根的二叉樹不平衡,要讓60平衡,只能將60左子樹的高度減少一層,右子樹增加一層,即將左子樹往上提,這樣60轉下來,因為60比30大,只能將其放在30的右子樹,而如果30有右子樹,右子樹根的值一定大于30,小于60,只能將其放在60的左子樹,旋轉完成后,更新節點的平衡因子即可。在旋轉過程中,有以下幾種情況需要考慮:1. 30節點的右孩子可能存在,也可能不存在2. 60可能是根節點,也可能是子樹如果是根節點,旋轉完成后,要更新根節點如果是子樹,可能是某個節點的左子樹,也可能是右子樹*/void _RotateR(PNode pParent){// pSubL: pParent的左孩子// pSubLR: pParent左孩子的右孩子PNode pSubL = pParent->_pLeft;PNode pSubLR = pSubL->_pRight;// 旋轉完成之后,30的右孩子作為雙親的左孩子pParent->_pLeft = pSubLR;// 如果30的左孩子的右孩子存在,更新親雙親if (pSubLR)pSubLR->_pParent = pParent;// 60 作為 30的右孩子pSubL->_pRight = pParent;// 因為60可能是棵子樹,因此在更新其雙親前必須先保存60的雙親PNode pPParent = pParent->_pParent;// 更新60的雙親pParent->_pParent = pSubL;// 更新30的雙親pSubL->_pParent = pPParent;// 如果60是根節點,根新指向根節點的指針if (NULL == pPParent){_pRoot = pSubL;pSubL->_pParent = NULL;}else{// 如果60是子樹,可能是其雙親的左子樹,也可能是右子樹if (pPParent->_pLeft == pParent)pPParent->_pLeft = pSubL;elsepPParent->_pRight = pSubL;}// 根據調整后的結構更新部分節點的平衡因子pParent->_bf = pSubL->_bf = 0;}
};
2. 新節點插入較高右子樹的右側——右右:左單旋
實現參考右單旋。
3. 新節點插入較高左子樹的右側——左右:先左單旋再右單旋
將雙旋變成單旋后再旋轉,即:先對30進行左單旋,然后再對90進行右單旋,旋轉完成后再考慮平衡因子的更新。
#define _CRT_SECURE_NO_WARNINGS 1#include <iostream>
using namespace std;template<class T>
struct AVLTreeNode
{AVLTreeNode(const T& data): _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr), _data(data), _bf(0){}AVLTreeNode<T>* _pLeft; // 該節點的左孩子AVLTreeNode<T>* _pRight; // 該節點的右孩子AVLTreeNode<T>* _pParent; // 該節點的雙親T _data;int _bf; // 該節點的平衡因子bool Insert(const T& data){// 1. 先按照二叉搜索樹的規則將節點插入到AVL樹中// 2. 新節點插入后,AVL樹的平衡性可能會遭到破壞,// 此時就需要更新平衡因子,并檢測是否破壞了AVL樹的平衡性/*pCur插入后,pParent的平衡因子一定需要調整,在插入之前,pParent的平衡因子分為三種情況:-1,0, 1, 分以下兩種情況:1. 如果pCur插入到pParent的左側,只需給pParent的平衡因子-1即可2. 如果pCur插入到pParent的右側,只需給pParent的平衡因子+1即可此時:pParent的平衡因子可能有三種情況:0,正負1, 正負21. 如果pParent的平衡因子為0,說明插入之前pParent的平衡因子為正負1,插入后被調整成0,此時滿足AVL樹的性質,插入成功2. 如果pParent的平衡因子為正負1,說明插入前pParent的平衡因子一定為0,插入后被更新成正負1,此時以pParent為根的樹的高度增加,需要繼續向上更新3. 如果pParent的平衡因子為正負2,則pParent的平衡因子違反平衡樹的性質,需要對其進行旋轉處理*/while (pParent){// 更新雙親的平衡因子if (pCur == pParent->_pLeft)pParent->_bf--;elsepParent->_bf++;// 更新后檢測雙親的平衡因子if (0 == pParent->_bf){break;}else if (1 == pParent->_bf || -1 == pParent->_bf){// 插入前雙親的平衡因子是0,插入后雙親的平衡因為為1 或者 -1 ,說明以雙親為根的二叉樹// 的高度增加了一層,因此需要繼續向上調整pCur = pParent;pParent = pCur->_pParent;}else{// 雙親的平衡因子為正負2,違反了AVL樹的平衡性,需要對以pParent// 為根的樹進行旋轉處理if (2 == pParent->_bf){// ...}else{// ...}}}return true;}//1. 新節點插入較高左子樹的左側——左左:右單旋/*在插入前,AVL樹是平衡的,新節點插入到30的左子樹(注意:此處不是左孩子)中,30左子樹增加了一層,導致以60為根的二叉樹不平衡,要讓60平衡,只能將60左子樹的高度減少一層,右子樹增加一層,即將左子樹往上提,這樣60轉下來,因為60比30大,只能將其放在30的右子樹,而如果30有右子樹,右子樹根的值一定大于30,小于60,只能將其放在60的左子樹,旋轉完成后,更新節點的平衡因子即可。在旋轉過程中,有以下幾種情況需要考慮:1. 30節點的右孩子可能存在,也可能不存在2. 60可能是根節點,也可能是子樹如果是根節點,旋轉完成后,要更新根節點如果是子樹,可能是某個節點的左子樹,也可能是右子樹*/void _RotateR(PNode pParent){// pSubL: pParent的左孩子// pSubLR: pParent左孩子的右孩子PNode pSubL = pParent->_pLeft;PNode pSubLR = pSubL->_pRight;// 旋轉完成之后,30的右孩子作為雙親的左孩子pParent->_pLeft = pSubLR;// 如果30的左孩子的右孩子存在,更新親雙親if (pSubLR)pSubLR->_pParent = pParent;// 60 作為 30的右孩子pSubL->_pRight = pParent;// 因為60可能是棵子樹,因此在更新其雙親前必須先保存60的雙親PNode pPParent = pParent->_pParent;// 更新60的雙親pParent->_pParent = pSubL;// 更新30的雙親pSubL->_pParent = pPParent;// 如果60是根節點,根新指向根節點的指針if (NULL == pPParent){_pRoot = pSubL;pSubL->_pParent = NULL;}else{// 如果60是子樹,可能是其雙親的左子樹,也可能是右子樹if (pPParent->_pLeft == pParent)pPParent->_pLeft = pSubL;elsepPParent->_pRight = pSubL;}// 根據調整后的結構更新部分節點的平衡因子pParent->_bf = pSubL->_bf = 0;}//3. 新節點插入較高左子樹的右側——左右:先左單旋再右單旋// 旋轉之前,60的平衡因子可能是-1/0/1,旋轉完成之后,根據情況對其他節點的平衡因子進行調整void _RotateLR(PNode pParent){PNode pSubL = pParent->_pLeft;PNode pSubLR = pSubL->_pRight;// 旋轉之前,保存pSubLR的平衡因子,旋轉完成之后,需要根據該平衡因子來調整其他節點的平衡因子int bf = pSubLR->_bf;// 先對30進行左單旋_RotateL(pParent->_pLeft);// 再對90進行右單旋_RotateR(pParent);if (1 == bf)pSubL->_bf = -1;else if (-1 == bf)pParent->_bf = 1;}};
4. 新節點插入較高右子樹的左側——右左:先右單旋再左單旋
參考左右雙旋。
總結:
假如以pParent為根的子樹不平衡,即pParent的平衡因子為2或者-2,分為以下情況考慮:
1. pParent的平衡因子為2,說明pParent的右子樹高,設pParent的右子樹的根為pSubR。
- 當pSubR的平衡因子為1時,執行左單旋。
- 當pSubR的平衡因子為-1時,執行右左單旋。
2.?pParent的平衡因子為-2,說明pParent的左子樹高,設pParent的左子樹的根為pSubL。
- 當pSubL的平衡因子為-1時,執行右單旋。
- 當pSubL的平衡因子為1時,執行左右單旋。
旋轉完成后,原pParent為根的子樹高度降低,已經平衡,不需要再向上更新。
2.5 -> AVL樹的驗證
AVL樹是在二叉搜索樹的基礎上加入了平衡性的限制,因此要驗證AVL樹,可以分為兩步:
1. 驗證其為二叉搜索樹
????????如果中序遍歷可以得到一個有序的序列,就說明其為二叉搜索樹。
2. 驗證其為平衡樹
- 每個節點子樹高度差的絕對值不超過1(注意節點中如果沒有平衡因子)。
- 節點的平衡因子是否計算正確。
#define _CRT_SECURE_NO_WARNINGS 1#include <iostream>
using namespace std;template<class T>
struct AVLTreeNode
{AVLTreeNode(const T& data): _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr), _data(data), _bf(0){}AVLTreeNode<T>* _pLeft; // 該節點的左孩子AVLTreeNode<T>* _pRight; // 該節點的右孩子AVLTreeNode<T>* _pParent; // 該節點的雙親T _data;int _bf; // 該節點的平衡因子bool Insert(const T& data){// 1. 先按照二叉搜索樹的規則將節點插入到AVL樹中// 2. 新節點插入后,AVL樹的平衡性可能會遭到破壞,// 此時就需要更新平衡因子,并檢測是否破壞了AVL樹的平衡性/*pCur插入后,pParent的平衡因子一定需要調整,在插入之前,pParent的平衡因子分為三種情況:-1,0, 1, 分以下兩種情況:1. 如果pCur插入到pParent的左側,只需給pParent的平衡因子-1即可2. 如果pCur插入到pParent的右側,只需給pParent的平衡因子+1即可此時:pParent的平衡因子可能有三種情況:0,正負1, 正負21. 如果pParent的平衡因子為0,說明插入之前pParent的平衡因子為正負1,插入后被調整成0,此時滿足AVL樹的性質,插入成功2. 如果pParent的平衡因子為正負1,說明插入前pParent的平衡因子一定為0,插入后被更新成正負1,此時以pParent為根的樹的高度增加,需要繼續向上更新3. 如果pParent的平衡因子為正負2,則pParent的平衡因子違反平衡樹的性質,需要對其進行旋轉處理*/while (pParent){// 更新雙親的平衡因子if (pCur == pParent->_pLeft)pParent->_bf--;elsepParent->_bf++;// 更新后檢測雙親的平衡因子if (0 == pParent->_bf){break;}else if (1 == pParent->_bf || -1 == pParent->_bf){// 插入前雙親的平衡因子是0,插入后雙親的平衡因為為1 或者 -1 ,說明以雙親為根的二叉樹// 的高度增加了一層,因此需要繼續向上調整pCur = pParent;pParent = pCur->_pParent;}else{// 雙親的平衡因子為正負2,違反了AVL樹的平衡性,需要對以pParent// 為根的樹進行旋轉處理if (2 == pParent->_bf){// ...}else{// ...}}}return true;}//1. 新節點插入較高左子樹的左側——左左:右單旋/*在插入前,AVL樹是平衡的,新節點插入到30的左子樹(注意:此處不是左孩子)中,30左子樹增加了一層,導致以60為根的二叉樹不平衡,要讓60平衡,只能將60左子樹的高度減少一層,右子樹增加一層,即將左子樹往上提,這樣60轉下來,因為60比30大,只能將其放在30的右子樹,而如果30有右子樹,右子樹根的值一定大于30,小于60,只能將其放在60的左子樹,旋轉完成后,更新節點的平衡因子即可。在旋轉過程中,有以下幾種情況需要考慮:1. 30節點的右孩子可能存在,也可能不存在2. 60可能是根節點,也可能是子樹如果是根節點,旋轉完成后,要更新根節點如果是子樹,可能是某個節點的左子樹,也可能是右子樹*/void _RotateR(PNode pParent){// pSubL: pParent的左孩子// pSubLR: pParent左孩子的右孩子PNode pSubL = pParent->_pLeft;PNode pSubLR = pSubL->_pRight;// 旋轉完成之后,30的右孩子作為雙親的左孩子pParent->_pLeft = pSubLR;// 如果30的左孩子的右孩子存在,更新親雙親if (pSubLR)pSubLR->_pParent = pParent;// 60 作為 30的右孩子pSubL->_pRight = pParent;// 因為60可能是棵子樹,因此在更新其雙親前必須先保存60的雙親PNode pPParent = pParent->_pParent;// 更新60的雙親pParent->_pParent = pSubL;// 更新30的雙親pSubL->_pParent = pPParent;// 如果60是根節點,根新指向根節點的指針if (NULL == pPParent){_pRoot = pSubL;pSubL->_pParent = NULL;}else{// 如果60是子樹,可能是其雙親的左子樹,也可能是右子樹if (pPParent->_pLeft == pParent)pPParent->_pLeft = pSubL;elsepPParent->_pRight = pSubL;}// 根據調整后的結構更新部分節點的平衡因子pParent->_bf = pSubL->_bf = 0;}//3. 新節點插入較高左子樹的右側——左右:先左單旋再右單旋// 旋轉之前,60的平衡因子可能是-1/0/1,旋轉完成之后,根據情況對其他節點的平衡因子進行調整void _RotateLR(PNode pParent){PNode pSubL = pParent->_pLeft;PNode pSubLR = pSubL->_pRight;// 旋轉之前,保存pSubLR的平衡因子,旋轉完成之后,需要根據該平衡因子來調整其他節點的平衡因子int bf = pSubLR->_bf;// 先對30進行左單旋_RotateL(pParent->_pLeft);// 再對90進行右單旋_RotateR(pParent);if (1 == bf)pSubL->_bf = -1;else if (-1 == bf)pParent->_bf = 1;}//驗證是否為AVL樹int _Height(PNode pRoot);bool _IsBalanceTree(PNode pRoot){// 空樹也是AVL樹if (nullptr == pRoot) return true;// 計算pRoot節點的平衡因子:即pRoot左右子樹的高度差int leftHeight = _Height(pRoot->_pLeft);int rightHeight = _Height(pRoot->_pRight);int diff = rightHeight - leftHeight;// 如果計算出的平衡因子與pRoot的平衡因子不相等,或者// pRoot平衡因子的絕對值超過1,則一定不是AVL樹if (diff != pRoot->_bf || (diff > 1 || diff < -1))return false;// pRoot的左和右如果都是AVL樹,則該樹一定是AVL樹return _IsBalanceTree(pRoot->_pLeft) && _IsBalanceTree(pRoot->_pRight);}};
2.6 -> AVL樹的性能
AVL樹是一棵絕對平衡的二叉搜索樹,其要求每個節點的左右子樹高度差的絕對值都不超過1,這樣可以保證查詢時高效的時間復雜度,即O(n)。但是如果要對AVL樹做一些結構修改的操作,性能非常低下,比如:插入時要維護其絕對平衡,旋轉的次數比較多,更差的是在刪除時,有可能一直要讓旋轉持續到根的位置。因此:如果需要一種查詢高效且有序的數據結構,而且數據的個數為靜態的(即不會改變),可以考慮AVL樹。
感謝各位大佬支持!!!
互三啦!!!