網站首頁 編程語言 正文
引子:AVL樹是因為什么出現的?
二叉搜索樹可以縮短查找的效率,如果數據有序或接近有序二叉搜索樹將退化為單支樹,查找元素相當于在順序表中搜索元素,效率低下時間復雜度:O(N)
兩位俄羅斯的數學家G.M.Adelson-Velskii和E.M.Landis在1962年 發明了一種解決上述問題的方法:當向二叉搜索樹中插入新結點后,如果能保證每個結點的左右子樹高度之差的絕對值不超過1(對樹中的結點進行調整),即為AVl樹以他們的名字縮寫命名也可以叫高度二叉搜索樹
1.AVl樹的的特性
一棵AVL樹或者是空樹,或者是具有以下性質的二叉搜索樹,它就是AVL樹。
- 它的左右子樹都是AVL樹
- 左右子樹高度之差(簡稱平衡因子)的絕對值不超過1(-1/0/1),節點右子樹最長路徑-左子樹最長路徑
如果AVl樹有n個結點,其高度可保持在O(logN) ,搜索時間復雜度O(logN),為什么?
答:左右子樹高度之差的絕對值不超過1,那么只有最后一層會差一部分的節點;
2.AVl樹的框架
template<class K, class V>
struct AVLtreeNode
{
//節點構造函數
AVLtreeNode(const pair<K, V>& kv)
:_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_bf(0)
,_kv(kv)
{}
//節點的成員
//三叉鏈
AVLtreeNode<K, V>* _left;
AVLtreeNode<K, V>* _right;
AVLtreeNode<K, V>* _parent;
int _bf;//平衡因子
//數據使用庫里面的pair類存儲的kv
pair<K, V> _kv;
};
template<class K,class V>
class AVLtree
{
typedef AVLtreeNode<K, V> Node;
public:
//構造函數
AVLtree()
:_root(nullptr)
{}
//四種旋轉
void RotateL(Node* parent)
void RotateR(Node* parent)
void RotateLR(Node* parent)
void RotateRL(Node* parent)
//插入
bool Insert(const pair<K, V>& kv)
//尋找
Node* Find(const K& kv)
private:
Node* _root;
};
三叉鏈是什么?
3.AVL樹的插入?
bool Insert(const pair<K, V>& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
Node* parent = _root, *cur = _root;
while (cur)
{
//找nulptr,如果已經有這個key了,二叉搜索樹的特性不支持冗余,所以返回失敗
if (cur->_kv.first > kv.first)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_kv.first <kv.first)
{
parent = cur;
cur = cur->_right;
}
else
{
return false;
}
}
//
cur = new Node(kv);
//判斷孩子在父親的左邊還是右邊
if (cur->_kv.first > parent->_kv.first)
{
parent->_right = cur;
cur->_parent = parent;
}
else
{
parent->_left = cur;
cur->_parent = parent;
}
while (parent)
{
//影響一條路徑所有的祖先
if (parent->_right == cur)
parent->_bf++;
else
parent->_bf--;
if (parent->_bf == 0)
{
//左右平衡了不會再影響祖先了
break;
}
if (parent->_bf == 1 || parent->_bf == -1)
{
//當前節點所在子樹變了,會影響父親
// 繼續往上更新
cur = parent;
parent = parent->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -2)
{
//parent所在子樹已經不平衡,需要旋轉處理一下
if (parent->_bf == -2)
{
if (cur->_bf == -1)
// 右單旋
RotateR(parent);
else // cur->_bf == 1
RotateLR(parent);
}
else // parent->_bf == 2
{
if (cur->_bf == 1)
// 左單旋
RotateL(parent);
else // cur->_bf == -1
RotateRL(parent);
}
break;
}
else
{
// 插入節點之前,樹已經不平衡了,或者bf出錯。需要檢查其他邏輯
assert(false);
}
}
return true;
}
插入整體邏輯:
- 如果還沒有元素是一課空樹,直接插入即可;如果有元素,按pair的first(key)和比較的節點比較結果為大說明為空的哪個位置在右邊,和比較的節點比較的結果小說明為空的哪個位置在左邊,如果相等說明已經有這個元素了,二叉搜索樹不支持冗余返回一個pair類第一個成員為那個相同元素的map的迭代器和第二個成員為false的pair類迭代器;
- 不知道這個已經找到的位置在父節點的左邊還是右邊,需要判斷一下,然后插入元素;
- 插入元素的后那么平衡因子將發生變化,為0說明這個父親節點左右平衡不會影響其他節點,為1或者-1需要向上調整,為2或者-2說明已經不平衡需要旋轉;
節點右子樹最長路徑-左子樹最長路徑,右邊插入節點就+,左邊插入節點就-;
3.1四種旋轉(左單旋、右單旋、左右雙旋、右左雙旋)
3.1.1左單旋
- 調用函數是傳的參數是軸點
- 要保留軸點的父親,以及調整三叉鏈
- 調整后原來的孩子和父親(軸點)的平衡因子都置為0;
void RotateR(Node* parent)
{
//軸點的左,孩子節點
Node* subL = parent->_left;
//孩子節點的右
Node* subLR = subL->_right;
//我的右當你(軸點)的左
parent->_left = subLR;
//調整三叉鏈
if (subLR)
subLR->_parent = parent;
//你(軸點)做我的右
subL->_right = parent;
//調整三叉鏈
Node* parentParent = parent->_parent;
parent->_parent = subL;
if (parent == _root)
{
_root = subL;
_root->_parent = nullptr;
}
else
{
//軸點的父親新的孩子節點
if (parentParent->_left == parent)
parentParent->_left = subL;
else
parentParent->_right = subL;
subL->_parent = parentParent;
}
subL->_bf = parent->_bf = 0;
}
3.1.2右單旋
- 調用函數是傳的參數是軸點
- 要保留軸點的父親,以及調整三叉鏈
- 調整后原來的孩子和父親(軸點)的平衡因子都置為0;
void RotateL(Node* parent)
{
//軸點的右,孩子節點
Node* subR = parent->_right;
//孩子節點的左
Node* subRL = subR->_left;
//我的左當你(軸點)的右
parent->_right = subRL;
//調整三叉鏈
if (subRL)
{
subRL->_parent = parent;
}
//你(軸點)做我的左
subR->_left = parent;
Node* parentparent = parent->_parent;
parent->_parent = subR;
if (parent == _root)
{
if (parentparent->_left == parent)
parentparent->_left = subR;
else
parentparent->_right = subR;
subR->_parent = parentparent;
}
else
{
subR->_parent = nullptr;
_root = subR;
}
subR->_bf = parent->_bf = 0;
}
?3.1.3左右雙旋
- 調用左單旋是傳的參數是軸點1,右單旋傳的軸點2
- 平衡因子分3種情況,依靠3個被改變節點中最后一個來判斷
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotateL(parent->_left);
RotateR(parent);
// ...平衡因子調節還需要具體分析
if (bf == -1)
{
subL->_bf = 0;
parent->_bf = 1;
subLR->_bf = 0;
}
else if (bf == 1)
{
parent->_bf = 0;
subL->_bf = -1;
subLR->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = 0;
subL->_bf = 0;
subLR->_bf = 0;
}
else
{
assert(false);
}
}
依靠3個被改變節點中最后一個來判斷
3.1.4右左雙旋?
- 調用右單旋是傳的參數是軸點1,左單旋傳的軸點2
- 平衡因子分3種情況,依靠3個被改變節點中最后一個來判斷
void RotateRL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;
RotateR(parent->_right);
RotateL(parent);
// 平衡因子更新
if (bf == 1)
{
subR->_bf = 0;
parent->_bf = -1;
subRL->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 0;
subR->_bf = 1;
subRL->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = 0;
subR->_bf = 0;
subRL->_bf = 0;
}
else
{
assert(false);
}
}
附:AVL的性能
AVL樹是一棵絕對平衡的二叉搜索樹,其要求每個節點的左右子樹高度差的絕對值都不超過1,這樣可以保證查詢時高效的時間復雜度,即log2(N)
但是如果要對AVL樹做一些結構修改的操作,性能非常低下,比如:
插入時要維護其絕對平衡,旋轉的次數比較多,更差的是在刪除時,有可能一直要讓旋轉持續到根的位置。因此:如果需要一種查詢高效且有序的數據結構,而且數據的個數為靜態的(即不會改變),可以考慮AVL樹,但一個結構經常修改,就不太適合。
總結
- 調用旋轉的實參是軸點
- 左單旋:我的左當你的右,你(軸點)當我的左
- 右單旋:我的右當你的左,你(軸點)當我的右
原文鏈接:https://blog.csdn.net/m0_72964546/article/details/127634329
相關推薦
- 2022-09-29 Shell之function函數的定義及調用示例_linux shell
- 2023-11-19 如何將電腦復制的內容粘貼進MobaXterm?如何復制粘貼
- 2022-04-01 k8s no matches for kind “Ingress“ in version “exte
- 2023-06-16 Qt6實現調用攝像頭并顯示畫面_C 語言
- 2022-09-15 C++關于/2和>>1的區別說明_C 語言
- 2023-11-23 寶塔數據庫過大導入失效解決方案
- 2023-03-20 C#?Path類---文件路徑解讀_C#教程
- 2021-12-03 找不到或無法加載主類 CMD || 找不到\*\*\路徑|| 原因大全
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支