C++/數據結構:AVL樹

目錄

一、AVL樹的概念

二、AVL樹的實現

2.1節點定義

?2.2節點插入

三、AVL樹的旋轉

3.1新節點插入較高左子樹的左側:右單旋

3.2新節點插入較高右子樹的右側:左單旋

3.3新節點插入較高左子樹的右側---左右:先左單旋再右單旋

3.4新節點插入較高右子樹的左側---右左:先右單旋再左單旋

四、AVL樹的性能


一、AVL樹的概念

二叉搜索樹雖可以縮短查找的效率,但如果數據有序或接近有序二叉搜索樹將退化為單支樹,查
找元素相當于在順序表中搜索元素,效率低下。因此,兩位俄羅斯的數學家G.M. A delson- V elskii
和E.M. L andis在1962年 發明了一種解決上述問題的方法:當向二叉搜索樹中插入新結點后,如果能保證每個結點的左右 子樹高度之差的絕對值不超過1(需要對樹中的結點進行調整),即可降低樹的高度,從而減少平均 搜索長度。
一棵AVL樹或者是空樹,或者是具有以下性質的二叉搜索樹:
它的左右子樹都是AVL樹 左右子樹高度之差(簡稱平衡因子)的絕對值不超過1(-1/0/1)
如果一棵二叉搜索樹是高度平衡的,它就是AVL樹。如果它有n個結點,其高度可保持在
$O(log_2 n)$,搜索時間復雜度logn。

二、AVL樹的實現

2.1節點定義

template <class K,class V>
class AVLtreeNode
{AVLtreeNode<K, V>* _left;AVLtreeNode<K, V>* _right;AVLtreeNode<K, V>* _parent;pair<K, V> _kv;int bf;AVLtreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), bf(0){}
};

?2.2節點插入

AVL樹就是在二叉搜索樹的基礎上引入了平衡因子,因此AVL樹也可以看成是二叉搜索樹。那么
AVL樹的插入過程可以分為兩步:
1. 按照二叉搜索樹的方式插入新節點
2. 調整節點的平衡因子
新節點插入后,AVL樹的平衡性可能會遭到破壞,此時就需要更新平衡因子,并檢測是否破壞了AVL樹的平衡性。
pCur插入后,pParent的平衡因子一定需要調整,在插入之前,pParent的平衡因子分為三種情況:-1,0, 1, 分以下兩種情況:
?1. 如果pCur插入到pParent的左側,只需給pParent的平衡因子-1即可
?2. 如果pCur插入到pParent的右側,只需給pParent的平衡因子+1即可
此時:pParent的平衡因子可能有三種情況:0,正負1, 正負2
?1. 如果pParent的平衡因子為0,說明插入之前pParent的平衡因子為正負1,插入后被調整成0,此時滿足 AVL樹的性質,插入成功
?2. 如果pParent的平衡因子為正負1,說明插入前pParent的平衡因子一定為0,插入后被更新成正負1,此時以pParent為根的樹的高度增加,需要繼續向上更新
?3. 如果pParent的平衡因子為正負2,則pParent的平衡因子違反平衡樹的性質,需要對其進行旋轉處理
bool insert(const pair<K, V>& kv){if (_root == nullptr){_root = new(kv);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){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 (parent->_kv.first>cur->_kv.first){parent->_left = cur;}else{parent->_right = cur;} cur->_parent = parent;//調整avl樹的結構,處理parent的平衡因子while (parent){if (parent->left == cur){parent->bf--;}else if (parent->right == cur){parent->bf++;}//不斷向上更改avl樹中的bf平衡因子if (parent->_bf == 1 || parent->_bf == -1){//更新去上面的父節點的bfparent = parent->_parent;cur = cur->parent;}else if (parent->_bf == 2 || parent->_bf == -2){//旋轉處理//單旋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樹的旋轉

如果在一棵原本是平衡的AVL樹中插入一個新節點,可能造成不平衡,此時必須調整樹的結構,使之平衡化。根據節點插入位置的不同,AVL樹的旋轉分為四種:

3.1新節點插入較高左子樹的左側:右單旋

上圖在插入前,AVL樹是平衡的,新節點插入到30的左子樹(注意:此處不是左孩子)中,30左子樹增加了一層,導致以60為根的二叉樹不平衡,要讓60平衡,只能將60左子樹的高度減少一層,右子樹增加一層,即將左子樹往上提,這樣60轉下來,因為60比30大,只能將其放在30的右子樹,而如果30有右子樹,右子樹根的值一定大于30,小于60,只能將其放在60的左子樹,旋轉完成后,更新節點的平衡因子即可。在旋轉過程中,有以下幾種情況需要考慮:
1. 30節點的右孩子可能存在,也可能不存在。
2. 60可能是根節點,也可能是子樹如果是根節點,旋轉完成后,要更新根節點如果是子樹,可能是某個節點的左子樹,也可能是右子樹。
void RotateR(Node* parent)//右單旋{Node* subL = parent->_left;Node* subLR = subL->_right;Node* pparent = parent->_parent;parent->_left = subLR;if (subLR) subLR->_parent = parent;subL->_right = parent;parent->_parent = subL;if (pparent == nullptr){_root = subL;subL->_parent = nullptr;}else{if (pparent->_left == parent){pparent->_left == subL;}else{pparent->_right == subL;}subL->_parent = pparent;}subL->_bf = parent->_bf = 0;return true;}void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent);  if (bf == 1)//左邊高的右邊高,新增節點在右{parent->_bf = 0;subLR->_bf = 0;subL->_bf = -1;}else if (bf == -1)//新增節點在左{parent->_bf = 1;subLR->_bf = 0;subL->_bf = 0 ;}else if (bf == 0)//本身就是新增節點直接導致出現左邊高的右邊高{parent->_bf = 0;subLR->_bf = 0;subL->_bf = 0;}else{assert(false);}}

3.2新節點插入較高右子樹的右側:左單旋

和右單旋的思路以及實現方式大相徑庭。只需略微改動。

void RotateL(Node* parent)//左單旋{Node* subR = parent->right;Node* subRL = subR->left;Node* pparent = parent->_parent;parent->right = subRL;if(subRL)subRL->_parent = parent;subR->left = parent;parent->_parent = subR;if (pparent == nullptr){_root = subR;_root->_parent = nullptr;}else{if (pparent->_left == parent){pparent->_left = subR;}else{pparent->_right = subR;}subR->_parent = pparent;}parent->_bf = subR->_bf = 0;}

3.3新節點插入較高左子樹的右側---左右:先左單旋再右單旋

將雙旋變成單旋后再旋轉,即:先對30進行左單旋,然后再對90進行右單旋,旋轉完成后再
考慮平衡因子的更新。
void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent);  if (bf == 1)//左邊高的右邊高,新增節點在右{parent->_bf = 0;subLR->_bf = 0;subL->_bf = -1;}else if (bf == -1)//新增節點在左{parent->_bf = 1;subLR->_bf = 0;subL->_bf = 0 ;}else if (bf == 0)//本身就是新增節點直接導致出現左邊高的右邊高{parent->_bf = 0;subLR->_bf = 0;subL->_bf = 0;}else{assert(false);}}

3.4新節點插入較高右子樹的左側---右左:先右單旋再左單旋

參考右左雙旋

void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL =subR->_left;int bf = subRL->_bf;RotateR(parent->_right);RotateL(parent);if (bf == 1){parent->_bf = -1;subRL->_bf = 0;subR->_bf = 0;}else if (bf == -1){parent->_bf = 0;subRL->_bf = 0;subR->_bf = 1;}else if (bf == 0){parent->_bf = 0;subRL->_bf = 0;subR->_bf = 0;}else{assert(false);}}
總結:
假如以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為根的子樹個高度降低,已經平衡,不需要再向上更新。

四、AVL樹的性能

因為AVL樹也是二叉搜索樹,可按照二叉搜索樹的方式將節點刪除,然后再更新平衡因子,只不 錯與刪除不同的時,刪除節點后的平衡因子更新,最差情況下一直要調整到根節點的位置。

AVL樹是一棵絕對平衡的二叉搜索樹,其要求每個節點的左右子樹高度差的絕對值都不超過1,這 樣可以保證查詢時高效的時間復雜度,即$log_2 (N)$。但是如果要對AVL樹做一些結構修改的操作,性能非常低下,比如:插入時要維護其絕對平衡,旋轉的次數比較多,更差的是在刪除時, 有可能一直要讓旋轉持續到根的位置。因此:如果需要一種查詢高效且有序的數據結構,而且數 據的個數為靜態的(即不會改變),可以考慮AVL樹,但一個結構經常修改,就不太適合。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/714919.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/714919.shtml
英文地址,請注明出處:http://en.pswp.cn/news/714919.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Rocky Linux 運維工具 Systemd

一、Systemd 的簡介 Systemd是一個用于管理Linux系統啟動進程和服務的系統和服務管理器&#xff0c;取代了傳統的init系統。它提供了并行啟動、依賴關系管理、動態加載服務文件等功能&#xff0c;成為現代Linux發行版中主流的初始化系統。 二、Systemd 的參數說明 [Unit] Des…

SLAM基礎知識-卡爾曼濾波

前言&#xff1a; 在SLAM系統中&#xff0c;后端優化部分有兩大流派。一派是基于馬爾科夫性假設的濾波器方法&#xff0c;認為當前時刻的狀態只與上一時刻的狀態有關。另一派是非線性優化方法&#xff0c;認為當前時刻狀態應該結合之前所有時刻的狀態一起考慮。 卡爾曼濾波是…

SD NAND:為車載顯示器注入智能與安全的心臟

SD NAND 在車載顯示器的應用 在車載顯示器上&#xff0c;SD NAND&#xff08;Secure Digital NAND&#xff09;可以有多種應用&#xff0c;其中一些可能包括&#xff1a; 導航數據存儲&#xff1a; SD NAND 可以用于存儲地圖數據、導航軟件以及車載系統的相關信息。這有助于提…

微服務day03-Nacos配置管理與Nacos集群搭建

一.Nacos配置管理 Nacos不僅可以作為注冊中心&#xff0c;可以進行配置管理 1.1 統一配置管理 統一配置管理可以實現配置的熱更新&#xff08;即不用重啟當服務發生變更時也可以直接更新&#xff09; dataId格式&#xff1a;服務名-環境名.yaml&#xff0c;分組一般使用默認…

InnoDB高級特性篇(5)-使用InnoDB的全文索引

InnoDB是MySQL數據庫的一個關系型存儲引擎。它提供了很多強大的功能&#xff0c;其中一個重要的功能是全文索引。全文索引允許我們在文本數據中進行高效的搜索&#xff0c;以找到包含特定關鍵詞的記錄。在本文中&#xff0c;我們將詳細介紹如何在InnoDB中使用全文索引。 首先&…

藍橋杯備戰刷題two(自用)

1.楊輝三角形 #include<iostream> using namespace std; #define ll long long const int N2e510; int a[N]; //1 0 0 0 0 0 0 //1 1 0 0 0 0 0 //1 2 1 0 0 0 0 //1 3 3 1 0 0 0 //1 4 6 4 1 0 0 //1 5 10 10 5 1 //前綴和思想 //第一列全為1,第二列為從0開始遞增1的序…

信息檢索(七):Transformer Memory as a Differentiable Search Index

Transformer Memory as a Differentiable Search Index 摘要1. 引言2. 相關工作3. 可微搜索索引3.1 索引策略3.1.1 索引方法3.1.2 文檔表示策略 3.2 用于檢索的 Docids 表示3.3 訓練和優化 4. 實驗4.1 基線4.2 實驗結果 5. 結論參考資料 原文鏈接&#xff1a;https://proceedin…

Revit-二開之創建線性尺寸標注-(5)

創建線性尺寸標注 對應的Revit界面的按鈕 線性尺寸標注源碼 本篇文章實現的邏輯是從rvt文章中拾取一面墻,然后對墻添加再水平方向上的線性尺寸標注 protected override Result OnExecute(ExternalCommandData commandData, ref string message, ElementSet elements

LeetCode 刷題 [C++] 第55題.跳躍游戲

題目描述 給你一個非負整數數組 nums &#xff0c;你最初位于數組的 第一個下標 。數組中的每個元素代表你在該位置可以跳躍的最大長度。 判斷你是否能夠到達最后一個下標&#xff0c;如果可以&#xff0c;返回 true &#xff1b;否則&#xff0c;返回 false 題目分析 題目中…

2.1 mov、add和sub加減指令實操體驗

匯編語言 1. mov操作 1.1 mov移動值 mov指令把右邊的值移動到左邊 mount c d:masm c: debug r ax 0034 r 073f:0100 mov ax,7t1.2 mov移動寄存器的值 把右邊寄存器的值賦值給左邊的寄存器 a 073f:0105 mov bx,axt1.3 mov高八位&#xff08;high&#xff09;和低八位&am…

設計模式——中介者模式(mediator pattern)

概述 如果在一個系統中對象之間的聯系呈現為網狀結構&#xff0c;如下圖所示。對象之間存在大量的多對多聯系&#xff0c;將導致系統非常復雜&#xff0c;這些對象既會影響別的對象&#xff0c;也會被別的對象所影響&#xff0c;這些對象稱為同事對象&#xff0c;它們之間通過彼…

json---->如何把對象以json的形式傳遞給后端?

把對象以json的形式傳遞有很多種&#xff0c;先寫一種&#xff0c;后期再補充 &#x1f64c;&#x1f64c;&#x1f64c;&#x1f64c;&#x1f64c;&#x1f64c;&#x1f64c;&#x1f64c;&#x1f64c;&#x1f64c;&#x1f64c;&#x1f64c;&#x1f64c;&#x1f64c;&…

?用細節去解釋,如何打造一款行政旗艦車型

高山行政加長版應該是這個級別里最大的幾款 MPV 之一了&#xff0c;對于一款較大的車型&#xff0c;其最重要的是解決行駛的便利性。 這次我們就試試魏牌高山行政加長版&#xff0c;從產品本身出發看幾個緯度的細節&#xff1a; 行政該如何定義加長后產品的功能變化加長之后到…

Ladder類創建梯形對象共享一個下底

package Absent;public class Chapter5 {public static void main(String[] args) {Ladder.bottom100;Ladder ladderOnenew Ladder();Ladder ladderTwonew Ladder();ladderOne.top23;ladderTwo.top34;System.out.println("ladderOne的上底&#xff1a;"ladderOne.get…

代碼隨想錄算法訓練營(動態規劃9)|198.打家劫舍 213.打家劫舍II 337.打家劫舍III

今天就是打家劫舍的一天,微笑 198.打家劫舍 leetcode題目鏈接 視頻講解 文章講解 動規五部曲分析如下&#xff1a; 確定dp數組&#xff08;dp table&#xff09;以及下標的含義 dp[i]&#xff1a;考慮下標i&#xff08;包括i&#xff09;以內的房屋&#xff0c;最多可以偷竊…

Java 數組(詳細)

目錄 一、數組的概述 1. 數組的理解&#xff1a; 2. 數組相關的概念&#xff1a; 3. 數組的特點&#xff1a; 4. 數組的分類&#xff1a; 5.數據結構&#xff1a; 二、一維數組 1. 一維數組的聲明與初始化 2. 一維數組元素的引用&#xff1a; 3. 數組的屬性&#xff1…

Scikit-Learn邏輯回歸

Scikit-Learn邏輯回歸 1、邏輯回歸概述1.1、邏輯回歸1.2、邏輯回歸的優缺點1.3、邏輯回歸與線性回歸2、邏輯回歸的原理2.1、邏輯回歸的概念與原理2.2、邏輯回歸的損失函數2.3、梯度下降法求解邏輯回歸的最優解3、Scikit-Learn邏輯回歸3.1、決策邊界3.2、Scikit-Learn邏輯回歸AP…

【Java數據結構 -- 二叉樹+樹的深度優先遍歷】

二叉樹 1. 二叉樹1.1 二叉樹的介紹1.2 兩種特殊的二叉樹1.3 二叉樹的性質1.4 二叉樹的存儲 2. 二叉樹的基本操作2.1 二叉樹的創建2.2 二叉樹的優先遍歷2.3 遞歸實現二叉樹遍歷2.4 用非遞歸實現二叉樹遍歷 1. 二叉樹 1.1 二叉樹的介紹 二叉樹是一種數據結構&#xff0c;一顆二…

【python、nlp、transformer】transformer學習部分

注&#xff1a; 此博文僅為了解transformer架構&#xff0c;如果使用&#xff0c;建議直接調用庫就行了 Transformer的優勢 相比之前占領市場的LSTM和GRU模型&#xff0c;Transformer有兩個顯著的優勢&#xff1a; 1. Transformer能夠利用分布式GPU進行并行訓練&#xff0c…

小朋友來自多少小區 - 華為OD統一考試(C卷)

OD統一考試&#xff08;C卷&#xff09; 分值&#xff1a; 100分 題解&#xff1a; Java / Python / C 題目描述 幼兒園組織活動&#xff0c;老師布置了一個任務&#xff1a; 每個小朋友去了解與自己同一個小區的小朋友還有幾個。 我們將這些數量匯總到數組 garden 中。 請…