【C++】:AVL樹

朋友們、伙計們,我們又見面了,本期來給大家解讀一下有關多態的知識點,如果看完之后對你有一定的啟發,那么請留下你的三連,祝大家心想事成!

C 語 言 專 欄:C語言:從入門到精通

數據結構專欄:數據結構

個? 人? 主? 頁?:stackY、

C + + 專 欄? ?:C++

Linux 專?欄? :Linux

目錄

1. AVL樹的概念

2. AVL樹節點的定義

3. AVL樹的插入

3.1 AVL樹的旋轉

1. 右單旋

2. 左單旋

3. 先左單旋再右單旋(雙旋)

4. 先右單旋再左單旋(雙旋)

4. AVL樹的驗證?

4.1 AVL樹的性能?


1. AVL樹的概念

二叉搜索樹雖可以縮短查找的效率,但如果數據有序或接近有序二叉搜索樹將退化為單支樹,查找元素相當于在順序表中搜索元素,效率低下。因此,兩位俄羅斯的數學家G.M.Adelson-Velskii和E.M.Landis在1962年發明了一種解決上述問題的方法:

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

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

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

如果一棵二叉搜索樹是高度平衡的,它就是AVL樹。如果它有n個結點,其高度可保持在
O(\log N),搜索時間復雜度O(\log N)

2. AVL樹節點的定義

在這里我們定義AVL樹使用一個pair來存儲數據,關于AVL樹其中除了左右子樹的節點指針,還需要一個記錄父親的節點指針,并且需要存儲一個平衡因子。平衡因子是右子樹的高度減去左子樹的高度。

//AVL樹節點的定義
template<class K, class V>
struct AVLTreeNode
{AVLTreeNode<K, V>* _left;   //左子樹AVLTreeNode<K, V>* _right;  //右子樹AVLTreeNode<K, V>* _parent; //父節點pair<K, V> _kv;             //存儲節點數據的kv模型int _bf;                    //平衡因子//AVLTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr),_parent(nullptr),_kv(kv),_bf(0){}
};

3. AVL樹的插入

AVL樹就是在二叉搜索樹的基礎上引入了平衡因子,因此AVL樹也可以看成是二叉搜索樹。那么AVL樹的插入過程可以分為兩步:

  • 1. 按照二叉搜索樹的方式插入新節點
  • 2. 調整節點的平衡因子

如果插入的節點在左邊,那么就需要將平衡因子--,如果插入的節點在右邊,平衡因子就需要++,并且需要注意的是當平衡因子改變之后,如果為0,代表平衡,不需要其他操作,如果改變之后為1或者-1,那么同樣的也需要對它祖先的平衡因子進行改變,直到它的父節點為空即可停止,如果改變之后的平衡因子為-2或者2,那么就表示出現的不平衡現象,需要進行旋轉。

//插入
bool Insert(const pair<K,V>& kv){//1. 先按照二叉搜索樹的規則將節點插入到AVL樹中Node* cur = _root;Node* parent = nullptr;if (_root == nullptr){_root = new Node(kv);retrun true;}while (cur){if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else if (cur->_kv.first < kv.first){parent = cur;cur = cur->rigth;}elseretrun false;}cur = new Node(kv);if (parent->_kv.first > kv.first){parent->_left = cur;cur->_parent = parent;}else{parent->_right = cur;cur->_parent = parent;}//2. 調整節點的平衡因子while (parent){if (parent->_left == cur)    //插入的節點在左邊時,平衡因子--parent->_bf--;else (parent->_right == cur) //插入的節點在右邊時,平衡因子++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;}

3.1 AVL樹的旋轉

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


1. 右單旋

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

需要注意的一個點,我們不確定這棵樹是不是另外一棵樹的一個子樹,所以還需要將parent的父親記錄下來,如果這棵樹就是獨立的,那么只需要將subL設置為新的根節點即可,如果是另外一棵樹的子樹,那么就需要將旋轉完之后的樹鏈接在它的祖先上。

首先將根節點記為parent,因為需要右旋,所以肯定是左邊高往右邊旋轉,所以將parent的左子樹記為subL,將subL的右子樹記為subLR,接下來就需要需要旋轉了,將parent的左指向subLR,然后將subL的右指向parent,這樣子就完成了右旋。

需要注意的是在修改完各各節點的鏈接時,它們原來的父親關系就需要重新設置,比如上面的圖,將parent的父親指向subL,將subLR的父親指向parent(subLR不一定為空,所以需要判斷一下再進行鏈接)此時只需要將各各節點的平衡因子修改即可,在右旋之后可以發現subL和parent的平衡因子都變成了0,所以直接對它們各自的平衡因子修改即可

//右單旋void Rotate_right(Node* parent){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 (parent == _root){_root = subL;subL->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = subL;}else{ppNode->_right = subL;}subL->_parent = ppNode;}//修改平衡因子subL->_bf = parent->_bf = 0;}
2. 左單旋

左單旋和右單旋的情況類似,只不過左單旋是右邊高往左邊旋轉,類似的可以參考右單旋的思路。

//左單旋void Rotate_left(Node* parent){Node* subR = parent->_right;  //右子樹的節點Node* subRL = subR->_left;    //Node* ppNode = parent->_parent;//旋轉->重新鏈接subR->_left = parent;parent->_parent = subR;parent->_right = subRL;if (subRL)subRL->_parent = parent;//鏈接祖先if (_root == parent){_root = subR;subR->_parent = nullptr;}else{if (ppNode->_left == parent)ppNode->_left = subR;elseppNode->_right = subR;subR->_parent = ppNode;}//修改平衡因子parent->_bf = subR->_bf = 0;}
3. 先左單旋再右單旋(雙旋)

左右雙旋在這里依舊存在三種情況:

①插入在subLR的左邊

②插入在subLR的右邊

③subLR就是新插入的節點

雙旋的情況可以看到是一個折線的樣子,根據偏轉的方向來確定首先向哪邊旋轉,先將根節

點記為parent,再將parent的左記為subL,將subL的右記為subLR,先以subL為根進行左單旋,然后再以parent為根進行右單旋。然后根據上述三種情況修改平衡因子。

可以根據subLR的平衡因子來修改parent、subL、subLR的平衡因子:

①如果subLR的平衡因子是-1,那么在雙旋完之后,需要將parent的平衡因子修改為1,將其他兩個修改為0。

②如果subLR的平衡因子是1,那么在雙旋完之后,需要將subL的平衡因子修改為-1,將其他兩個修改為0。

③如果subLR的平衡因子是0,那么parent、subL、subLR的平衡因子修改為0。

//左右雙旋void Rotate_left_right(Node* parent){Node* subL = parent->-left;Node* subLR = subL->_right;//記錄插入之后的平衡因子int bf = subLR->_bf;//先左旋Rotate_left(subL);//再右旋Rotate_right(parent);//修改平衡因子if (bf == 0)   //本身就是新插入的節點{subL->_bf = subLR->_bf = parent->_bf = 0;}else if (bf == -1)  //左邊插入{parent->_bf = 1;subL->_bf = 0;subLR->_bf = 0;}else if (bf == 1)  //右邊插入{subL->_bf = -1;parent->_bf = 0;subLR->_bf = 0;}else{assert(flase);}}
4. 先右單旋再左單旋(雙旋)

同樣的這里也存在三種情況:

①插入在subRL的左邊

②插入在subRL的右邊

③subRL就是新插入的節點

右左雙旋的邏輯和左右雙旋的邏輯一樣,可以參考上面的。

//右左雙旋void Rotate_right_left(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;Rotate_right(subR);Rotate_left(parent);if (bf == 0){subR->_bf = subRL->_bf = parent->_bf = 0;}else if (bf == -1){subR->_bf = 1;parent->_bf = 0;subRL->_bf = 0;}else if (bf == 1){parent->_bf = -1;subR->_bf = 0;subRL->_bf = 0;}elseassert(false);}

解決完插入中的旋轉問題之后我們將旋轉融入到插入的整個代碼中:

bool Insert(const pair<K,V>& kv){//1. 先按照二叉搜索樹的規則將節點插入到AVL樹中Node* cur = _root;Node* parent = nullptr;if (_root == nullptr){_root = new Node(kv);retrun true;}while (cur){if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else if (cur->_kv.first < kv.first){parent = cur;cur = cur->rigth;}elseretrun false;}cur = new Node(kv);if (parent->_kv.first > kv.first){parent->_left = cur;cur->_parent = parent;}else{parent->_right = cur;cur->_parent = parent;}//2. 調整節點的平衡因子while (parent){if (parent->_left == cur)    //插入的節點在左邊時,平衡因子--parent->_bf--;else (parent->_right == cur) //插入的節點在右邊時,平衡因子++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){//平衡出現差錯,需要進行旋轉調整if (parent->_bf == 2 && cur->_bf == 1)         //左單旋情況Rotate_left(parent);else if (parent->_bf == -2 && cur->_bf == -1)  //右單旋情況Rotate_right(parent);else if (parent->_bf == -2 && cur->_bf == 1)   //左右雙旋情況Rotate_left_right(parent);else if (parent->_bf == 2 && cur->_bf == -1)   //右左雙旋情況Rotate_right_left(parent);// 1、旋轉讓這顆子樹平衡了// 2、旋轉降低了這顆子樹的高度,恢復到跟插入前一樣的高度,所以對上一層沒有影響,不用繼續更新break;}else   //如果平衡因子不為上述情況,那么就不能再繼續了assert(false);}return true;}

4. AVL樹的驗證?

AVL樹是在二叉搜索樹的基礎上加入了平衡性的限制,因此要驗證AVL樹,可以分兩步:

  • 1. 驗證其為二叉搜索樹

如果中序遍歷可得到一個有序的序列,就說明為二叉搜索樹

  • 2. 驗證其為平衡樹

每個節點子樹高度差的絕對值不超過1(注意節點中如果沒有平衡因子)
節點的平衡因子是否計算正確
?

//判斷是否平衡bool IsBalance(){return _IsBalance(_root);}//計算樹的高度int _Height(Node* root){if (root == nullptr)return 0;int leftHeight = _Height(root->_left);   //左子樹的高度int rightHeight = _Height(root->_right); //右子樹的高度//返回左右子樹高度較高的那一顆樹+1return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;}bool _IsBalance(Node* root){if (root == nullptr)return true;int leftHeight = _Height(root->_left);   //左子樹的高度int rightHeight = _Height(root->_right); //右子樹的高度if (rightHeight - leftHeight != root->_bf){cout << root->_kv.first << "平衡因子異常" << endl;return false;}//右子樹-左子樹高度不超過2則為AVL樹return abs(rightHeight - leftHeight) < 2&& _IsBalance(root->_left)&& _IsBalance(root->_right);}

4.1 AVL樹的性能?

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

?

朋友們、伙計們,美好的時光總是短暫的,我們本期的的分享就到此結束,欲知后事如何,請聽下回分解~,最后看完別忘了留下你們彌足珍貴的三連喔,感謝大家的支持!??

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

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

相關文章

用python 網絡自動化統計交換機有多少端口UP

用python統計交換機有多少端口UP 用python統計交換機有多少端口UP&#xff0c;可以間接的反饋有多少個用戶在線。我們使用上次的腳本將可達的網絡設備ip統計到reachable_ip.txt中&#xff0c;這次我們使用reachable_ip.txt來登陸設備來統計多少端口是UP的 云配置 拓撲 交換機…

使用fcl庫做碰撞檢測

fcl庫是真難用&#xff0c;導入自己的項目的時候遇到各種坑。 第一個坑就是git clone并build fcl庫后生成的fcl-config.cmake里面有問題&#xff0c;需要在這里進行相應修改 set_and_check(FCL_INCLUDE_DIRS "/home/xxxx/fcl/build/include") set(FCL_LIBRARIES fc…

【Cisco Packet Tracer】VLAN通信 多臂/單臂路由/三層交換機

在進行本文的實驗之前&#xff0c;請確保掌握以下內容&#xff1a; 【Cisco Packet Tracer】交換機 學習/更新/泛洪/VLAN實驗 【Cisco Packet Tracer】路由器實驗 靜態路由/RIP/OSPF/BGP 【Cisco Packet Tracer】路由器 NAT實驗 本文介紹VLAN間的通信方法&#xff0c; 包括…

FreeRTOS的任務優先級、Tick以及狀態講解(尊敬的嵌入式工程師,不妨進來喝杯茶)

任務優先級和Tick 在FreeRTOS中&#xff0c;任務的優先級和Tick是兩個關鍵的概念&#xff0c;它們直接影響任務的調度和執行。 任務優先級 每個任務都被分配一個優先級&#xff0c;用于決定任務在系統中的調度順序。 優先級是一個無符號整數&#xff0c;通常從0開始&#xff0…

Mysql- 流程函數-(If, CASE WHEN)的使用及練習

目錄 4.1 If函數語法格式 4.2 CASE WHEN 條件表達式格式 4.3 update與 case when 4.4 練習題1 4.5 練習題2 4.6 練習題3-行轉列 4.7 牛客練習題 4.8 LeetCode練習題 4.1 If函數語法格式 IF(expr1,expr2,expr3) 解釋&#xff1a; 如果表達式expr1true(expr1 <>…

力扣第 119 場雙周賽(Java)

文章目錄 T1 找到兩個數組中的公共元素代碼解釋 T2 消除相鄰近似相等字符代碼解釋 T3 最多 K 個重復元素的最長子數組代碼解釋 T4 關閉分部的可行集合數目代碼解釋 鏈接&#xff1a;第 119 場雙周賽 - 力扣&#xff08;LeetCode&#xff09; T1 找到兩個數組中的公共元素 給你…

Xcode doesn’t support iOS 16.6

xocde版本低&#xff0c;手動放入16.6的依賴文件 https://gitee.com/qiu1993/iOSDeviceSupport/blob/master/iOS16/16.6.zip 路徑 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport

JAVA全棧開發 day21_JDBC與反射結合、設計模式

一、總結 一階段 day01 java 發展&#xff0c;java 環境( path, java_home, class_path)&#xff0c;java 原理&#xff0c; java 執行 &#xff0c; jvm , jre , jdk day02 變量 標識符命名規則 數據類型 數據類型的轉換 運算符 day03 選擇結構 if , switch day04 循環結…

分割回文串

分割回文串 描述 : 給你一個字符串 s&#xff0c;請你將 s 分割成一些子串&#xff0c;使每個子串都是 回文串 。返回 s 所有可能的分割方案。 回文串 是正著讀和反著讀都一樣的字符串。 題目 : LeetCode 131.分割回文串 : 131. 分割回文串 分析 : 字符串如何判斷回文本…

20 Redis進階 - 運維監控

1、理解Redis監控 Redis運維和監控的意義不言而喻&#xff0c;可以以下三個方面入手 1.首先是Redis自身提供了哪些狀態信息&#xff0c;以及有哪些常見的命令可以獲取Redis的監控信息; 2.一些常見的UI工具可以可視化的監控Redis; 3.理解Redis的監控體系;2、Redis自身狀態及命…

Vue3-02-ref() 響應式詳解

ref() 是什么 ref() 是一個函數&#xff1b; ref() 函數用來聲明響應式的狀態&#xff08;就是來聲明變量的&#xff09; ref() 函數聲明的變量&#xff0c;是響應式的&#xff0c;變量的值改變之后&#xff0c;頁面中會自動重新渲染。ref() 有什么特點 1.ref() 可以聲明基礎…

VUE語法--toRefs與toRef用法

1、功能概述 ref和reactive能夠定義響應式的數據&#xff0c;當我們通過reactive定義了一個對象或者數組數據的時候&#xff0c;如果我們只希望這個對象或者數組中指定的數據響應&#xff0c;其他的不響應。這個時候我們就可以使用toRefs和toRef實現局部數據的響應。 toRefs是…

算一算并輸出2到正整數n中每個數的質因子(for循環)

計算并輸出2到正整數n之間每個數的質因子&#xff0c;并以乘法形式輸出。 輸入格式: 輸入只有1個正整數即n。 輸出格式: 把2到正整數n間的每一個數分解成它的質因子&#xff0c;并以乘法的形式輸出。例如&#xff0c;輸入的正整數n值為10&#xff0c;則應輸出如下&#xff…

MIT線性代數筆記-第28講-正定矩陣,最小值

目錄 28.正定矩陣&#xff0c;最小值打賞 28.正定矩陣&#xff0c;最小值 首先正定矩陣是一個實對稱矩陣 由第 26 26 26講的末尾可知正定矩陣有以下四種判定條件&#xff1a; 所有特征值都為正左上角所有 k k k階子矩陣行列式都為正&#xff08; 1 ≤ k ≤ n 1 \le k \le n …

DDD系列 - 第6講 倉庫Repository及Mybatis、JPA的取舍(一)

目錄 一、領域層定義倉庫接口1.1 設計聚合1.2 定義倉庫Repository接口二 、基礎設施層實現倉庫接口2.1 設計數據庫2.2 集成Mybatis2.3 引入Convetor2.4 實現倉庫三、回顧一、領域層定義倉庫接口 書接上回,之前通過一個關于拆解、微服務、面向對象的故事,向大家介紹了如何從微…

簡單的WEB服務器

優質博文&#xff1a;IT-BLOG-CN 目的&#xff1a; 了解Java Web服務器是如何運行的。Web服務器使用HTTP與其客戶端&#xff0c;也就是Web瀏覽器進行通信。基于Java的Web服務器會使用兩個重要類&#xff1a;java.net.Socket類和java.net.ServerSocket類&#xff0c;并通過發送…

詳解Keras3.0 Models API: Model class

1、語法 keras.Model() 將不同層組為具有訓練/推理特征的對象的模型 2、示例一 inputs keras.Input(shape(37,)) x keras.layers.Dense(32, activation"relu")(inputs) outputs keras.layers.Dense(5, activation"softmax")(x) model keras.Model…

58.Nacos源碼分析2

三、服務心跳。 3.服務心跳 Nacos的實例分為臨時實例和永久實例兩種&#xff0c;可以通過在yaml 文件配置&#xff1a; spring:application:name: order-servicecloud:nacos:discovery:ephemeral: false # 設置實例為永久實例。true&#xff1a;臨時; false&#xff1a;永久ser…

MySQL-備份+日志:介質故障與數據庫恢復

目錄 第1關&#xff1a;備份與恢復 第2關&#xff1a;備份日志&#xff1a;介質故障的發生與數據庫的恢復 第1關&#xff1a;備份與恢復 任務描述 本關任務: 備份數據庫&#xff0c;然后再恢復它。 test1_1.sh # 你寫的命令將在linux的命令行運行 # 對數據庫residents作海…

【C/C++筆試練習】多態的概念、虛函數的概念、虛表地址、派生類的虛函數、虛函數的訪問、指針引用、動態多態、完全數計算、撲克牌大小

文章目錄 C/C筆試練習選擇部分&#xff08;1&#xff09;多態的概念&#xff08;2&#xff09;虛函數的概念&#xff08;3&#xff09;虛表地址&#xff08;4&#xff09;派生類的虛函數&#xff08;5&#xff09;虛函數的訪問&#xff08;6&#xff09;分析程序&#xff08;7&…