C++漫步結構與平衡的殿堂:AVL樹

文章目錄

  • 1.AVL樹的概念
  • 2.AVL樹的結構
  • 3.AVL樹的插入
  • 4.AVL樹的旋轉
    • 4.1 左單旋
    • 4.2 右單旋
    • 4.3 右左雙旋
    • 4.4 左右雙旋
  • 5.AVL樹的刪除
  • 6.AVL樹的高度
  • 7.AVL樹的平衡判斷
  • 希望讀者們多多三連支持
  • 小編會繼續更新
  • 你們的鼓勵就是我前進的動力!

二叉搜索樹有其自身的缺陷,假如往樹中插入的元素有序或者接近有序,二叉搜索樹就會退化成單支樹,時間復雜度會退化成 O(N),因此 mapset 等關聯式容器的底層結構是對二叉樹進行了平衡處理,即采用平衡樹來實現

1.AVL樹的概念

在這里插入圖片描述

我們已經從多種樹型結構走到現在,每一次變化都是為了提高搜索的效率,即時間復雜度

二叉搜索樹雖可以縮短查找的效率,但如果數據有序或接近有序二叉搜索樹將退化為單支樹,查找元素相當于在順序表中搜索元素,效率低下,因此發明了 AVL

那么什么是AVL樹呢?

當向二叉搜索樹中插入新結點后,如果能保證每個結點的左右子樹高度之差的絕對值不超過 1 (需要對樹中的結點進行調整),即可降低樹的高度,從而減少平均搜索長度

在這里插入圖片描述

一棵 AVL 樹或者是空樹,應該是具有以下性質的二叉搜索樹:

  • 它的左右子樹都是 AVL
  • 左右子樹高度之差(簡稱平衡因子)的絕對值不超過 1(-1/0/1)

二叉搜索樹在理想情況下時間復雜度與二叉平衡搜索樹相同,均為 O ( l o g 2 n ) O(log_2 n) O(log2?n),但在極端情況下二叉平衡搜索樹優于二叉搜索樹,因為二叉平衡搜索樹會自己調整平衡(后面會詳細解釋)

為什么是嚴格的絕對值為 1,不是 0 或者更大的數字?

若要求高度差為 0,即嚴格平衡,樹的結構會過于 rigid(僵化)。每次插入或刪除節點都可能需要大量調整操作,導致性能下降。允許高度差為 1,在保持較好平衡性的同時,減少了不必要的調整
若允許高度差為 2,樹的平衡性會明顯下降,可能出現一側子樹比另一側高很多的情況,導致查找等操作的時間復雜度增加
所以平衡因子為 1 是最合適的

2.AVL樹的結構

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;AVLTreeNode(const pair<K, V>& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr),_bf(0){ }
};
  • pair<K, V> _kv:用于存儲鍵值對,pairC++ 標準庫中的一個模板類,可將兩個不同類型的值組合在一起
  • AVLTreeNode<K, V>* _left:指向左子節點的指針
  • AVLTreeNode<K, V>* _right:指向右子節點的指針
  • AVLTreeNode<K, V>* _parent:指向父節點的指針,這在調整樹的平衡時很有用
  • int _bf:平衡因子(Balance Factor),用來記錄該節點左右子樹的高度差。平衡因子為 0 時表示左右子樹高度相等;為 1 時表示右子樹比左子樹高 1;為 -1 時表示左子樹比右子樹高 1

3.AVL樹的插入

	typedef AVLTreeNode<K, V> Node;
public: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 (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;}}//鏈接插入節點與AVL樹cur = new Node(kv);if (parent->_kv.first < kv.first){parent->_right = cur;}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){//旋轉調整(...)}else{assert(false);}}return true;}

AVL 樹的插入和二叉搜索樹是很像的,先根據左大右小的原則,尋找插入節點的位置,然后判斷父母節點與插入節點的關系,連接新節點,唯一不同的地方是平衡因子調節的部分,高度差是由右子樹減去左子樹得出的,可以總結出以下方法:

🚩 (1)新增在左,parent平衡因子減減
在這里插入圖片描述

🚩 (2)新增在右,parent平衡因子加加

在這里插入圖片描述

🚩 (3)更新后parent平衡因子 == 0

說明 parent 所在的子樹的高度不變,不會影響祖先,不用再繼續沿著到 root 的路徑往上更新,然后循環結束

🚩 (4)更新后parent平衡因子 == 1 or -1

說明 parent 所在的子樹的高度變化,會影響祖先,需要繼續沿著到 root 的路徑往上更新,循環繼續

🚩 (5)更新后parent平衡因子 == 2 or -2

說明 parent 所在的子樹的高度變化且不平衡,需要對parent所在子樹進行旋轉,讓他平衡,然后循環結束

🔥值得注意的是: 如果平衡因子出現比 2 還大,比 -2 還小的數,說明之前的插入就已經出問題了

4.AVL樹的旋轉

4.1 左單旋

void RotateL(Node* parent)
{Node* cur = parent->_right;Node* curleft = cur->_left;parent->_right = curleft;if (curleft){curleft->_parent = parent;}cur->_left = parent;Node* ppnode = parent->_parent;parent->_parent = cur;if (parent == _root){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}cur->_parent = ppnode;}parent->_bf = cur->_bf = 0;
}

以下將根據一個圖例來解釋如何進行的左單旋:

在這里插入圖片描述

左單旋顧名思義就是右子樹太長,需要向左旋轉形成平衡,平衡因子為 2 的節點定為 parent,其右節點為 curcur 的左節點為 curleft

  1. 調整 parent 的右子節點:parent 的右子節點設置成 curleft,若 curleft 不為空,就把 curleft 的父節點設置成 parent
  2. 調整 cur 的左子節點:cur 的左子節點設置成 parentppnodeparent 的父節點,把 parent 的父節點設置成 cur
  3. 調整根節點或者 ppnode 的子節點:parent 是根節點,那就把 cur 設為新的根節點,并且將 cur 的父節點設為 nullptr。若 parent 不是根節點,就依據 parentppnode 的左子節點還是右子節點,來更新 ppnode 的相應子節點為 cur,同時把 cur 的父節點設為 ppnode

4.2 右單旋

void RotateR(Node* parent)
{Node* cur = parent->_left;Node* curright = cur->_right;parent->_left = curright;if (curright){curright->_parent = parent;}Node* ppnode = parent->_parent;cur->_right = parent;parent->_parent = cur;if (ppnode == nullptr){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}cur->_parent = ppnode;}parent->_bf = cur->_bf = 0;
}

和左單旋類似,這里就不詳細解釋了

在這里插入圖片描述

4.3 右左雙旋

void RotateRL(Node* parent)
{Node* cur = parent->_right;Node* curleft = cur->_left;int bf = curleft->_bf;RotateR(parent->_right);RotateL(parent);if (bf == 0){cur->_bf = 0;curleft->_bf = 0;parent->_bf = 0;}else if (bf == 1){cur->_bf = 0;curleft->_bf = 0;parent->_bf = -1;}else if (bf == -1){cur->_bf = 1;curleft->_bf = 0;parent->_bf = 0;}else{assert(false);}
}

右左雙旋適用于新節點插入較高右子樹的左側的情況

在這里插入圖片描述
30parent 節點,90cur 節點,60curleft 節點

先以 90 進行右單旋,再以 30 進行左單旋

雙旋的重點是平衡節點的調整,根據多個例子可以知道,主要是看 curleft 節點的平衡因子

在這里插入圖片描述

如果原來 curleft 平衡因子為 0 ,即 curleft 為新增節點導致的雙旋,那么 curleftcurparent 平衡因子都為 0

在這里插入圖片描述

如果原來 curleft 平衡因子為 1 ,即在 curleft 右邊新增,那么 curcurleft 平衡因子都為 0parent 的平衡因子為 1

在這里插入圖片描述

如果原來 curleft 平衡因子為 -1 ,即在 curleft 左邊新增,那么 parentcurleft 平衡因子都為 0cur 的平衡因子為 1

4.4 左右雙旋

void RotateLR(Node* parent)
{Node* cur = parent->_left;Node* curright = cur->_right;int bf = curright->_bf;RotateL(parent->_left);RotateR(parent);if (bf == 0){parent->_bf = 0;cur->_bf = 0;curright->_bf = 0;}else if (bf == -1){parent->_bf = 1;cur->_bf = 0;curright->_bf = 0;}else if (bf == 1){parent->_bf = 0;cur->_bf = -1;curright->_bf = 0;}
}

和右左雙旋類似,這里就不詳細解釋了

在這里插入圖片描述

5.AVL樹的刪除

在實際開發中,雖然 AVL 樹是一種自平衡的二叉搜索樹,但其刪除操作通常不被優先實現

AVL 樹的核心特性是通過旋轉操作(左旋、右旋、左右旋、右左旋)來保證樹的高度平衡。在插入操作中,僅需從插入節點向上回溯至根節點,檢查并調整路徑上節點的平衡因子,最多進行兩次旋轉操作就能恢復樹的平衡。然而,刪除操作后,平衡的破壞可能會沿著從刪除節點到根節點的路徑向上傳播,導致需要多次旋轉操作來恢復平衡。這使得刪除操作的實現邏輯變得異常復雜,需要仔細處理各種可能的情況

而且實現插入刪除一般會使用 紅黑樹B樹 等更優的數據結構

6.AVL樹的高度

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;
}

比較左子樹和右子樹的高度,取較大值并加 1(加上當前根節點),得到當前子樹的高度

7.AVL樹的平衡判斷

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);
}

每遍歷一個節點就對其左右子樹的高度進行計算,然后判斷是否絕對值小于 2

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


希望讀者們多多三連支持

小編會繼續更新

你們的鼓勵就是我前進的動力!

請添加圖片描述

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

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

相關文章

Verilog Test Fixture 時鐘激勵

1、占空比50%時鐘產生 always begin<clock> 1b0 ;#<PERIOD/2> ;<clock> 1b1 ;#<PERIOD/2> ; end reg <clock> 1b0 ;alwaysbegin#<PERIOD/2> ;<clock> ~<clock> ;end 2…

從人體姿態到機械臂軌跡:基于深度學習的Kinova遠程操控系統架構解析

在工業自動化、醫療輔助、災難救援與太空探索等前沿領域&#xff0c;Kinova輕型機械臂憑借7自由度關節設計和出色負載能力脫穎而出。它能精準完成物體抓取、復雜裝配和精細操作等任務。然而&#xff0c;實現人類操作者對Kinova機械臂的直觀高效遠程控制一直是技術難題。傳統遠程…

探秘數據中臺:五大核心平臺的功能全景解析

數據中臺作為企業數據資產的 “智慧中樞”&#xff0c;通過整合數據處理全流程的核心功能&#xff0c;實現數據價值的深度挖掘與高效應用。以下從五大核心平臺出發&#xff0c;全面拆解數據中臺的功能架構與應用價值。 一、數據可視化平臺&#xff1a;讓數據 “開口說話” 1.…

深度 |提“智”向新,奔向未來——當前機器人產業觀察

機器人踏著“貓步”在T臺走秀、進入工廠協助造車&#xff0c;教育、醫療、城市管理等領域都有了機器人的幫助……今天&#xff0c;機器人已得到廣泛應用&#xff0c;走進你我的生活。 ?? 伴隨著技術日新月異&#xff0c;機器人產業加快提“智”向新。特別是今年以來&#xf…

橋隧坡災害監測報警:用科技筑起生命安全的“智能防線”

.2024年&#xff0c;梅大高速茶陽路段高邊坡塌方事件造成重大傷亡&#xff0c;舉國痛心。這場悲劇再次敲響警鐘&#xff1a;橋梁、隧道、邊坡等高風險區域的實時監測與精準報警&#xff0c;已成為交通安全的生命線。如何用技術手段在災害發生前“搶跑”&#xff0c;第一時間阻斷…

【Python】一鍵提取視頻音頻并生成MP3的完整指南 by `MoviePy`

摘要 昨天&#xff0c; 我在讓一個小朋友給我整理一次培訓的視頻的時候&#xff0c;我看到他把視頻文件放到剪映里面處理。 我以為他要干什么呢&#xff0c; 還很期待&#xff0c;結果他只是為了導出音頻而已。 于是就有了今天的這篇博客。 作為音視頻處理領域的常用需求&…

PDF轉長圖工具

市面上的PDF轉換工具數不勝數&#xff0c;福昕PDF、萬興PDF、Adobe Acrobat&#xff08;DC&#xff09;、PDF24等眾多軟件都具備PDF轉圖片的功能。然而&#xff0c;這些知名軟件大多只能將單頁PDF轉換為單張圖片&#xff0c;若要將PDF整體轉換為一張長圖&#xff0c;似乎并無此…

【Yolo精讀+實踐+魔改系列】Yolov3論文超詳細精講(翻譯+筆記)

前言 前面咱們已經把 YOLOv1 和 YOLOv2 的老底都給掀了&#xff0c;今天輪到 YOLOv3 登場&#xff0c;這可是 Joseph Redmon 的“封神之作”。講真&#xff0c;這哥們本來是搞學術的&#xff0c;結果研究的模型被某些軍方拿去“整點活”——不是做人是做武器的那種活。于是他一…

算法攻略:接雨水問題的深度解析

算法攻略:接雨水問題的深度解析 一、引言 在算法的領域中,“接雨水”問題是一道經典且富有挑戰性的題目。它不僅考查對數組操作的理解,更需要巧妙運用算法思想來解決看似復雜的實際場景問題。通過深入研究這一問題,我們能提升算法思維和編程能力,更好地應對各類算法難題。…

【Linux】Linux工具(1)

3.Linux工具&#xff08;1&#xff09; 文章目錄 3.Linux工具&#xff08;1&#xff09;Linux 軟件包管理器 yum什么是軟件包關于 rzsz查看軟件包——yum list命令如何安裝軟件如何卸載軟件補充——yum如何找到要安裝軟件的下載地址 Linux開發工具Linux編輯器-vim使用1.vim的基…

springboot項目tomcat中加載不了

Spring Boot項目在Tomcat中加載不了的問題可能由多種原因引起&#xff0c;包括打包方式不正確、依賴配置錯誤、啟動類配置不當等。以下是詳細的解決方案&#xff1a; 1. 修改項目打包形式 將項目打包形式從jar改為war&#xff0c;以確保項目以正確的格式被Tomcat加載。在pom.…

Matlab 數控車床進給系統的建模與仿真

1、內容簡介 Matlab217-數控車床進給系統的建模與仿真 可以交流、咨詢、答疑 2、內容說明 略 摘 要:為提高數控車床的加工精度,對數控 車床進給系統中影響加工精度的主要因素進行了仿真分析研 動系統的數學模型,利用MATLAB軟件中的動態仿真工具 究:依據機械動力學原理建立了…

Python Cookbook-7.8 使用 Berkeley DB 數據庫

任務 你想將一些數據做持久化處理&#xff0c;而且也想體驗一下BerkeleyDB數據庫的簡潔和高效。 解決方案 如果以前在你的計算機中安裝過 BerkeleyDB&#xff0c;Python標準庫附帶的bsddb包(以及可選的 bsddb3&#xff0c;用于訪間Berkeley DBrelease 3.2數據庫)可以被用來作…

QT6 源(82):閱讀與注釋日歷類型 QCalendar,本類并未完結,儒略歷,格里高利歷原來就是公歷,

&#xff08;1&#xff09;本代碼來自于頭文件 qcalendar . h &#xff1a; #ifndef QCALENDAR_H #define QCALENDAR_H#include <limits>#include <QtCore/qglobal.h> #include <QtCore/qlocale.h> #include <QtCore/qstring.h> #include <QtCore/…

【C/C++】字符函數和字符串函數

文章目錄 前言字符函數和字符串函數1.字符分類函數2.字符轉換函數3.strlen的使用和模擬實現3.1 代碼演示3.2 strlen返回值3.3 strlen的模擬實現 4.strcpy的使用和模擬實現4.1 代碼演示4.2 模擬實現 5.strcat的使用和模擬實現5.1 代碼演示5.2 模擬實現 6.strcmp的使用和模擬實現…

Spark-core-RDD入門

RDD基本概念 Resilient Distributed Dataset 叫做彈性分布式數據集&#xff0c;是Spark中最基本的數據抽象&#xff0c;是分布式計算的實現載體&#xff0c;代表一個不可變&#xff0c;可分區&#xff0c;里面的元素并行計算的集合。 - Dataset&#xff1a; 一個數據集合&…

緩存套餐-01.Spring Cache介紹和常用注解

一.Spring Cache 要使用直接導入坐標即可。 如何選擇底層的緩存實現呢&#xff1f;只要導入對應的緩存坐標即可。如果要使用redis作為緩存實現&#xff0c;那么只需要導入redis的maven坐標。 二.常用注解 Cacheable&#xff1a;不光往緩存中寫緩存數據&#xff0c;而且會從緩…

STM32智能空氣凈化器項目開發

一、項目概述 本空氣凈化器項目基于STM32F4系列微控制器&#xff0c;整合多傳感器數據采集、環境參數顯示、網絡通信及執行機構控制等功能&#xff0c;實現智能化空氣質量管理。項目采用FreeRTOS實時操作系統進行多任務調度&#xff0c;結合TFT觸摸屏實現人機交互&#xff0c;…

[數據處理] 6. 數據可視化

&#x1f44b; 你好&#xff01;這里有實用干貨與深度分享?? 若有幫助&#xff0c;歡迎&#xff1a;? &#x1f44d; 點贊 | ? 收藏 | &#x1f4ac; 評論 | ? 關注 &#xff0c;解鎖更多精彩&#xff01;? &#x1f4c1; 收藏專欄即可第一時間獲取最新推送&#x1f514;…

嵌入式學習筆記 - STM32 SRAM控制器FSMC

一 SRAM控制器內部結構圖&#xff1a; 以下以512K SRAM芯片為例 二 SRAM地址矩陣/尋址方式&#xff1a; SRAM的地址尋址方式通過行地址與列地址交互的方式存儲數據 三 STM32 地址映射 從STM32的地址映射中可以看出&#xff0c;FSMC控制器支持擴展4塊外部存儲器區域&#xff0…