1.AVL樹的概念
普通二叉搜索樹:二叉搜索樹
二叉搜索樹雖可以縮短查找的效率,但如果數(shù)據(jù)有序或接近有序普通的二叉搜索樹將退化為單支樹,查找元素相當(dāng)于在順序表中搜索元素,效率低下。因此,兩位俄羅斯的數(shù)學(xué)家G.M.Adelson-Velskii和E.M.Landis在1962年發(fā)明了一種解決上述問題的方法(AVL樹是以這兩位的名字命名的):當(dāng)向二叉搜索樹中插入新結(jié)點后,如果能保證每個結(jié)點的左右子樹高度之差的絕對值不超過1,超過了需要對樹中的結(jié)點進行調(diào)整(旋轉(zhuǎn)),即可降低樹的高度,從而減少平均搜索長度。
AVL樹也是二叉搜索樹,但有以下特點:
- 它的左右子樹都是AVL樹
- 左右子樹高度之差(簡稱平衡因子)的絕對值不超過1(-1/0/1)
- 如果一棵二叉搜索樹是高度平衡的,它就是AVL樹。如果它有n個結(jié)點,其高度可保持在 O ( l o g 2 n ) O(log_2 n) O(log2?n),搜索時間復(fù)雜度O( l o g 2 n log_2 n log2?n)。
2.平衡因子
AVL樹的實現(xiàn)有很多種,本文引入平衡因子來維持高度穩(wěn)定。
本文平衡因子的定義:右子樹高度 - 左子樹的高度。
依據(jù)每個節(jié)點的平衡因子,我們可以判斷樹的情況:
- 平衡因子在(-1, 0, 1),當(dāng)前節(jié)點所在子樹是穩(wěn)定的。
-
平衡因子為2或-2,當(dāng)前節(jié)點所在子樹是不穩(wěn)定的。
插入節(jié)點后平衡因子的更新:
- 插入節(jié)點在右子樹,平衡因子加一。
- 插入節(jié)點在左子樹,平衡因子減一。
插入節(jié)點后平衡因子的不同情況(重點):
- 當(dāng)前節(jié)點所在子樹平衡因子為0,子樹高度不變,不需要更新
(原來一邊高一邊低,新插入在低一方,變成完全平衡)。 - 當(dāng)前節(jié)點所在子樹平衡因子為1或-1,子樹高度變化,需要向上更新。
(原來完全平衡,現(xiàn)在一邊高,子樹整體高度加1,會影響到祖先的平衡,故需要向上更新看祖先所在子樹是否平衡) - 當(dāng)前節(jié)點所在子樹平衡因子為2或-2,子樹高度變化且不平衡,無需向上更新,對當(dāng)前子樹進行旋轉(zhuǎn)操作。
(當(dāng)前子樹已不平衡,向上更新沒有意義,旋轉(zhuǎn)操作是AVL樹的核心,可以降低當(dāng)前子樹的高度且不影響上面的樹結(jié)構(gòu))
3.節(jié)點的定義
節(jié)點除了需要增加一個平衡因子,還需要增加一個父親指針,方便我們進行平衡因子的向上更新和旋轉(zhuǎn)操作。
template<class K, class V>
struct ALVTreeNode
{
ALVTreeNode<K, V>* _left;
ALVTreeNode<K, V>* _right;
ALVTreeNode<K, V>* _parent;
pair<K, V> _kv; //存儲鍵值對
int _bf; //平衡因子
ALVTreeNode(const pair<K, V>& kv)
:_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_bf(0)
,_kv(kv)
{}
};
4.插入操作
PS:因為多加了一個父親指針,所以插入時要注意更新父親指針,平衡因子更新按前面的分析來還是比較簡單的,旋轉(zhuǎn)操作后面單獨講。
bool Insert(const pair<K, V>& kv)
{
if (_root == nullptr) //一開始為空樹,直接插入即可
{
_root = new Node(kv);
return true;
}
//找插入位置加插入
Node* cur = _root; //記錄插入位置
Node* parent = nullptr; //待插入位置的父親
while (cur)
{
if (kv.first > cur->_kv.first) //待插入節(jié)點在右子樹
{
parent = cur;
cur = cur->_right;
}
else if (kv.first < cur->_kv.first) //待插入節(jié)點在左子樹
{
parent = cur;
cur = cur->_left;
}
else //待插入節(jié)點已存在
{
return false;
}
}
cur = new Node(kv);
if (kv.first > parent->_kv.first) //插入在父親的右邊
{
parent->_right = cur;
}
else //插入在父親的左邊
{
parent->_left = cur;
}
cur->_parent = parent; //注意更新父親指針
//調(diào)整平衡因子
while (parent) //是有可能調(diào)整到根部的
{
if (cur == parent->_right) //如果新插入的在右子樹
{
parent->_bf++;
}
else if (cur == parent->_left) //如果新插入的在左子樹
{
parent->_bf--;
}
if (parent->_bf == 0) //插入后高度不變
{
break;
}
else if (parent->_bf == 1 || parent->_bf == -1) //插入后高度變化,但當(dāng)前子樹依然平衡,需要向上更新
{
parent = parent->_parent;
cur = cur->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -2) //插入后高度變化,并且當(dāng)前子樹已經(jīng)不平衡,旋轉(zhuǎn)
{
//旋轉(zhuǎn)操作(先省略)
break;
}
else //存在大于2小于-2的情況,樹原來就不平衡,應(yīng)該報錯
{
assert(false);
}
}
return true;
}
5.旋轉(zhuǎn)操作(重點)
5.1左單旋
主體思想:
平衡因子的調(diào)整:
代碼加細節(jié)處理:
void RotateL(Node* parent) //左單旋,rotate->旋轉(zhuǎn)
{
Node* SubR = parent->_right;
Node* SubRL = SubR->_left; //這個有可能為空
Node* ppnode = parent->_parent; //原來父親的父親
parent->_right = SubRL;
if(SubRL) SubRL->_parent = parent;
SubR->_left = parent;
parent->_parent = SubR;
if (ppnode == nullptr) //旋轉(zhuǎn)的是整顆樹
{
_root = SubR;
SubR->_parent = nullptr;
}
else //旋轉(zhuǎn)的是部分
{
if (ppnode->_left == parent) //是左子樹
{
ppnode->_left = SubR;
}
else //是右子樹
{
ppnode->_right = SubR;
}
SubR->_parent = ppnode;
}
//最后更新平衡因子
parent->_bf = SubR->_bf = 0;
}
5.2右單旋
PS:右單旋和左單旋類似,細節(jié)處理也差不多,這里只講主體思路。
主體思路:
平衡因子的調(diào)整:
代碼:
void RotateR(Node* parent) //右單旋細節(jié)處理和左單旋差不多
{
Node* SubL = parent->_left;
Node* SubLR = SubL->_right; //這個有可能為空
Node* ppnode = parent->_parent;
parent->_left = SubLR;
if(SubLR) SubLR->_parent = parent;
SubL->_right = parent;
parent->_parent = SubL;
if (ppnode == nullptr) //旋轉(zhuǎn)的是整顆樹
{
_root = SubL;
SubL->_parent = nullptr;
}
else //旋轉(zhuǎn)部分
{
if (ppnode->_left == parent) //是左子樹
{
ppnode->_left = SubL;
}
else //右子樹
{
ppnode->_right = SubL;
}
SubL->_parent = ppnode;
}
//最后更新平衡因子
parent->_bf = SubL->_bf = 0;
}
5.3左右雙旋
PS:雙旋其實就是兩次單旋(復(fù)用即可),有前面的基礎(chǔ)很好理解,重點在于旋轉(zhuǎn)后平衡因子的更新。
旋轉(zhuǎn)思路:
平衡因子的調(diào)整:
想知道平衡因子調(diào)整是那種情況,我們需要在旋轉(zhuǎn)前記錄SubRL的平衡因子bf:
- bf為0是第一種情況。
- bf為1是第二種情況。
- bf為-1是第三種情況。
代碼:
void RotateLR(Node* parent) //左右雙旋
{
Node* SubL = parent->_left;
Node* SubLR = SubL->_right;
int bf = SubLR->_bf;
RotateL(SubL);
RotateR(parent);
if (bf == 1) //插入的是右邊
{
SubLR->_bf = 0;
SubL->_bf = -1;
parent->_bf = 0;
}
else if (bf == -1) //插入的是左邊
{
SubLR->_bf = 0;
SubL->_bf = 0;
parent->_bf = 1;
}
else if (bf == 0) //剛好原來parent的左邊就兩個節(jié)點
{
SubLR->_bf = SubL->_bf = parent->_bf = 0;
}
else //原來就不是平衡樹,出現(xiàn)問題
{
assert(false);
}
}
5.4右左雙旋
PS:右左雙旋和左右雙旋思路是差不多的,重點還是在旋轉(zhuǎn)后平衡因子的更新。
旋轉(zhuǎn)思路:
平衡因子的調(diào)整:
和前面一樣,旋轉(zhuǎn)前確認SubRL的平衡因子bf即可。
代碼:
void RotateRL(Node* parent) //右左雙旋
{
Node* SubR = parent->_right;
Node* SubRL = SubR->_left;
int bf = SubRL->_bf;
RotateR(SubR);
RotateL(parent);
if (bf == 1) //插入的是右邊
{
SubRL->_bf = 0;
SubR->_bf = 0;
parent->_bf = -1;
}
else if (bf == -1) //插入的是左邊
{
SubRL->_bf = 0;
parent->_bf = 0;
SubR->_bf = 1;
}
else if (bf == 0) //原來parent的右邊就兩個節(jié)點
{
SubRL->_bf = SubR->_bf = parent->_bf = 0;
}
else //原來就有問題
{
assert(false);
}
}
文章來源:http://www.zghlxwxcb.cn/news/detail-756146.html
6.一些簡單的測試接口
int Height()
{
return _Height(_root);
}
int _Height(Node* root) //求高度的
{
if (root == nullptr)
return 0;
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
bool IsBalance()
{
return IsBalance(_root);
}
//看當(dāng)前樹是不是平衡樹
//(1)看每個子樹是否滿足左右子樹高度差不超過一
//(2)看平衡因子和所求的左右子樹高度差是否一致
bool IsBalance(Node* root)
{
if (root == nullptr)
return true;
int leftHight = _Height(root->_left);
int rightHight = _Height(root->_right);
if (rightHight - leftHight != root->_bf)
{
cout << "平衡因子異常:" << root->_kv.first << "->" << root->_bf << endl;
return false;
}
return abs(rightHight - leftHight) < 2
&& IsBalance(root->_left)
&& IsBalance(root->_right);
}
文章來源地址http://www.zghlxwxcb.cn/news/detail-756146.html
7.完整代碼
#pragma once
#include <iostream>
#include <assert.h>
#include <utility>
using namespace std;
template<class K, class V>
struct ALVTreeNode
{
ALVTreeNode<K, V>* _left;
ALVTreeNode<K, V>* _right;
ALVTreeNode<K, V>* _parent;
pair<K, V> _kv; //存儲鍵值對
int _bf;
ALVTreeNode(const pair<K, V>& kv)
:_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_bf(0)
,_kv(kv)
{}
};
template<class K, class V>
class AVLTree
{
public:
typedef ALVTreeNode<K, V> Node;
bool Insert(const pair<K, V>& kv)
{
if (_root == nullptr) //一開始為空樹,直接插入即可
{
_root = new Node(kv);
return true;
}
Node* cur = _root; //記錄插入位置
Node* parent = nullptr; //待插入位置的父親
while (cur)
{
if (kv.first > cur->_kv.first) //待插入節(jié)點在右子樹
{
parent = cur;
cur = cur->_right;
}
else if (kv.first < cur->_kv.first) //待插入節(jié)點在左子樹
{
parent = cur;
cur = cur->_left;
}
else //待插入節(jié)點已存在
{
return false;
}
}
cur = new Node(kv);
if (kv.first > parent->_kv.first) //插入在父親的右邊
{
parent->_right = cur;
}
else //插入在父親的左邊
{
parent->_left = cur;
}
cur->_parent = parent;
//調(diào)整平衡因子
while (parent) //是有可能調(diào)整到根部的
{
if (cur == parent->_right) //如果新插入的是右子樹
{
parent->_bf++;
}
else if (cur == parent->_left) //如果新插入的是左子樹
{
parent->_bf--;
}
if (parent->_bf == 0) //插入后高度不變
{
break;
}
else if (parent->_bf == 1 || parent->_bf == -1) //插入后高度變化,但當(dāng)前子樹依然平衡,需要向上更新
{
parent = parent->_parent;
cur = cur->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -2) //插入后高度變化,并且當(dāng)前子樹已經(jīng)不平衡,旋轉(zhuǎn)
{
//旋轉(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 //樹原來就不平衡,應(yīng)該報錯
{
assert(false);
}
}
return true;
}
/
///
int Height()
{
return _Height(_root);
}
int _Height(Node* root) //求高度的
{
if (root == nullptr)
return 0;
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
bool IsBalance()
{
return IsBalance(_root);
}
//看當(dāng)前樹是不是平衡樹
//(1)看每個子樹是否滿足左右子樹高度差不超過一
//(2)看平衡因子和所求的左右子樹高度差是否一致
bool IsBalance(Node* root)
{
if (root == nullptr)
return true;
int leftHight = _Height(root->_left);
int rightHight = _Height(root->_right);
if (rightHight - leftHight != root->_bf)
{
cout << "平衡因子異常:" << root->_kv.first << "->" << root->_bf << endl;
return false;
}
return abs(rightHight - leftHight) < 2
&& IsBalance(root->_left)
&& IsBalance(root->_right);
}
private:
void RotateL(Node* parent) //左單旋,rotate->旋轉(zhuǎn)
{
Node* SubR = parent->_right;
Node* SubRL = SubR->_left; //這個有可能為空
Node* ppnode = parent->_parent; //原來父親的父親
parent->_right = SubRL;
if(SubRL) SubRL->_parent = parent;
SubR->_left = parent;
parent->_parent = SubR;
if (ppnode == nullptr) //旋轉(zhuǎn)的是整顆樹
{
_root = SubR;
SubR->_parent = nullptr;
}
else //旋轉(zhuǎn)的是部分
{
if (ppnode->_left == parent) //是左子樹
{
ppnode->_left = SubR;
}
else //是右子樹
{
ppnode->_right = SubR;
}
SubR->_parent = ppnode;
}
//最后更新平衡因子
parent->_bf = SubR->_bf = 0;
}
void RotateR(Node* parent) //右單旋細節(jié)處理和左單旋差不多
{
Node* SubL = parent->_left;
Node* SubLR = SubL->_right; //這個有可能為空
Node* ppnode = parent->_parent;
parent->_left = SubLR;
if(SubLR) SubLR->_parent = parent;
SubL->_right = parent;
parent->_parent = SubL;
if (ppnode == nullptr) //旋轉(zhuǎn)的是整顆樹
{
_root = SubL;
SubL->_parent = nullptr;
}
else //旋轉(zhuǎn)部分
{
if (ppnode->_left == parent) //是左子樹
{
ppnode->_left = SubL;
}
else //右子樹
{
ppnode->_right = SubL;
}
SubL->_parent = ppnode;
}
//最后更新平衡因子
parent->_bf = SubL->_bf = 0;
}
void RotateLR(Node* parent) //左右雙旋
{
Node* SubL = parent->_left;
Node* SubLR = SubL->_right;
int bf = SubLR->_bf;
RotateL(SubL);
RotateR(parent);
if (bf == 1) //插入的是右邊
{
SubLR->_bf = 0;
SubL->_bf = -1;
parent->_bf = 0;
}
else if (bf == -1) //插入的是左邊
{
SubLR->_bf = 0;
SubL->_bf = 0;
parent->_bf = 1;
}
else if (bf == 0) //剛好原來parent的左邊就兩個節(jié)點
{
SubLR->_bf = SubL->_bf = parent->_bf = 0;
}
else //原來就不是平衡樹,出現(xiàn)問題
{
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) //插入的是右邊
{
SubRL->_bf = 0;
SubR->_bf = 0;
parent->_bf = -1;
}
else if (bf == -1) //插入的是左邊
{
SubRL->_bf = 0;
parent->_bf = 0;
SubR->_bf = 1;
}
else if (bf == 0) //原來parent的右邊就兩個節(jié)點
{
SubRL->_bf = SubR->_bf = parent->_bf = 0;
}
else //原來就有問題
{
assert(false);
}
}
Node* _root = nullptr;
};
到了這里,關(guān)于數(shù)據(jù)結(jié)構(gòu):AVL樹講解(C++)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!