平衡二叉搜索樹/AVL樹

VAL樹的特性

  1. 左右子樹高度差的絕對值不超過1。(即左右子樹高度差取值為-1,0,1)
  2. 且左右子樹均為VAL樹
  3. 右子樹的值大于左子樹的值

在搜索二叉樹中我們提及了搜索二叉樹的退化問題。

當有序(升序或降序)地插入時,搜索二叉樹就會出現下圖的情況,搜索次數從樹的高度次退化成n次。而平衡搜索二叉樹就解決了這一問題。

?

平衡方法

在解決方案中,左右子樹的差值被定為平衡因子,所以上述例子用平衡二叉搜索樹的角度來看如下圖。此時出現了不被允許的2和-2,意味著平衡被打破,需要進行調整平衡。

插入時平衡因子的變化邏輯

以下綠色方框代表一棵子樹,h為其高度,且h>=0,具體節點上方的數字為平衡因子。

因為插入節點,導致60這個節點的左右子樹失衡了。

?

????????如圖我們可以看到,根據插入位置的不同,其父節點的平衡因子的變化也不同。因為此處我使用的平衡因子 = 右子樹高度 - 左子樹高度。所以當插入到右子樹時,父節點平衡因子+1,插入到左子樹時,父節點平衡因子-1。

了解完插入時平衡因子的變化,我們再來了解一下變化的兩種情況。

(1)插入后父節點平衡因子變為1或-1的,那么其原來的平衡因子是0,意味著該父節點原本平衡,插入節點后打破了平衡使該父節點為根的整棵子樹高度產生變化,此時平衡因子需要繼續向上更新,于是60這個節點也受其影響改變了,此時出現了不被允許的失衡,此時就要旋轉調整平衡。

(2)第二種情況比較簡單,插入后父節點平衡因子變為0,意味著其原本有一些失衡,但還在允許范圍內,但插入后完全平衡了,此時無需再繼續更新,因為高度沒有改變。

總結:亞健康(-1、1)到健康(0)不用管,反之健康到亞健康要去向上更新平衡因子,因為其父節點可能直接就生病了。

// 在AVL樹中插入值為data的節點bool Insert(const T& data){if (_pRoot == nullptr){_pRoot = new Node(data);return true;}Node* parent = nullptr;Node* cur = _pRoot;while (cur){if (data > cur->_data){parent = cur;cur = cur->_pRight;}else if (data < cur->_data){parent = cur;cur = cur->_pLeft;}else{return false;}}cur = new Node(data);if (parent->_data < data){parent->_pRight = cur;}else{parent->_pLeft = cur;}cur->_pParent = parent;//調整平衡因子while (parent){if (cur == parent->_pLeft){parent->_bf--;}else{parent->_bf++;}//插入后恰好平衡了,跳出循環if (parent->_bf == 0){break;}    //高度出現變化但還未失衡,向上走進行判斷else if (parent->_bf == 1 || parent->_bf == -1){cur = parent;parent = parent->_pParent;}	//已經失衡需要進行調整else if (parent->_bf == 2 || parent->_bf == -2){if (parent->_bf == -2 && cur->_bf == -1){//右單旋RotateR(parent);}else if (parent->_bf == 2 && cur->_bf == 1){//左單旋RotateL(parent);}else if (parent->_bf == 2 && cur->_bf == -1){//右左雙旋RotateRL(parent);}else if (parent->_bf == -2 && cur->_bf == 1){//左右雙旋RotateLR(parent);}break;}else{//理論而言不可能出現這個情況(此時小于-2或大于2)assert(false);}}return true;}

旋轉調整?

有了前面插入時平衡因子變化邏輯的鋪墊,失衡時的情況就簡化成了:

失衡節點無非是-2或2,造就失衡節點的子節點無非是-1,1(產生高度變化)。排列組合后就出現了四種情況。于是出現四種旋轉方式與其一一對應。

?右單旋

此時的失衡情況為:左左(插入在失衡節點的左節點的左子樹中),此時失衡節點平衡因子為-2,產生高度變化的子節點平衡因子為-1。

所以右單旋適用于“左左”,即parent的平衡因子為-2,高度變化的子節點的平衡因子為-1。

?右單旋過程:
?

可以看到,調整后30和60這兩個節點的平衡因子都為0了。

那么為什么要這么做??

現在我們先來分析一下他們的大小關系。

比較大小后發現,60非常適合當作30的右子樹根節點,同時又做b子樹的父節點,因為60>b子樹任意值>30。

所以右單旋就是將失衡節點及其右子樹下壓,當作高度出現變化的節點的右子樹,再連接上b子樹和60這個節點,就使30的左右子樹高度一致了。左子樹高度是h+1(1為插入的節點),右邊也是h+1(1為原父節點),此時30成為新父節點。

// 右單旋void RotateR(Node* pParent){Node* subL = pParent->_pLeft;Node* subLR = subL->_pRight;pParent->_pLeft = subLR;if (subLR)subLR->_pParent = pParent;subL->_pRight = pParent;Node* temp = pParent->_pParent;pParent->_pParent = subL;//pParent有可能為根,右單旋后應該更新根節點指向。if (pParent == _pRoot){_pRoot = subL;_pRoot->_pParent = nullptr;}else{if (temp->_pLeft == pParent){temp->_pLeft = subL;}else{temp->_pRight = subL;}subL->_pParent = temp;}pParent->_bf = subL->_bf = 0;}

左單旋

與右單旋同理,不過多贅述。

左單旋適用于“右右”,即parent的平衡因子為2,高度變化的子節點的平衡因子為1。

旋轉過程:?

// 左單旋void RotateL(Node* pParent){Node* subR = pParent->_pRight;Node* subRL = subR->_pLeft;pParent->_pRight = subRL;if (subRL)subRL->_pParent = pParent;subR->_pLeft = pParent;Node* temp = pParent->_pParent;pParent->_pParent = subR;//pParent有可能為根,右單旋后應該更新根節點指向。if (pParent == _pRoot){_pRoot = subR;_pRoot->_pParent = nullptr;}else{if (temp->_pLeft == pParent){temp->_pLeft = subR;}else{temp->_pRight = subR;}subR->_pParent = temp;}pParent->_bf = subR->_bf = 0;}

左右雙旋

如圖所示,當出現插入節點在失衡節點(90)其左孩子(30)的右孩子(60)下的子樹時,就需要用到左右雙旋,單旋是無法解決的。

即:失衡節點平衡因子為-2,且其左孩子的平衡因子為1時使用左右雙旋,以下簡稱“左右”。

第一步,我們對30為根的子樹進行左單旋。(第一次旋轉后,失衡的狀況從“左右”變成了可以使用右單旋的“左左”)

?第二步,我們對90為根的子樹進行右單旋。

?

?代碼實際上是對左單旋和右單旋的復用,然后再處理節點間的連接關系。

// 左右雙旋void RotateLR(Node* pParent){Node* subL = pParent->_pLeft;Node* subLR = subL->_pRight;int bf = subLR->_bf;RotateL(subL);RotateR(pParent);subLR->_bf = 0;if (bf == 1){pParent->_bf = 0;subL->_bf = -1;}else if (bf == -1){pParent->_bf = 1;subL->_bf = 0;}else{pParent->_bf = 0;subL->_bf = 0;}}

右左雙旋

失衡節點平衡因子為2,且其左孩子的平衡因子為-1時使用左右雙旋。

第一步,我們對90為根的子樹進行右單旋。(第一次旋轉后,失衡的狀況從“右左”變成了可以使用左單旋的“右右”)

?第二步,我們對30為根的子樹進行左單旋。

?

// 右左雙旋void RotateRL(Node* pParent){Node* subR = pParent->_pRight;Node* subRL = subR->_pLeft;int bf = subRL->_bf;RotateR(subR);RotateL(pParent);subRL->_bf = 0;if (bf == 1){pParent->_bf = -1;subR->_bf = 0;}else if (bf == -1){pParent->_bf = 0;subR->_bf = 1;}else{pParent->_bf = 0;subR->_bf = 0;}}

完整代碼實現

#pragma once
#include<assert.h>
#include<vector>
#include<iostream>
using namespace std;namespace VAL
{template<class T>struct AVLTreeNode{AVLTreeNode(const T& data = T()): _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr), _data(data), _bf(0){}AVLTreeNode<T>* _pLeft;AVLTreeNode<T>* _pRight;AVLTreeNode<T>* _pParent;T _data;int _bf;   // 節點的平衡因子};// AVL: 二叉搜索樹 + 平衡因子的限制template<class T>class AVLTree{typedef AVLTreeNode<T> Node;public:AVLTree(): _pRoot(nullptr){}Node* Find(const T& data){Node* cur = _pRoot;while (cur){if (cur->_data < data){cur = cur->_pRight;}else if (cur->_data > data){cur = cur->_pLeft;}else{return cur;}}return nullptr;}// 在AVL樹中插入值為data的節點bool Insert(const T& data){if (_pRoot == nullptr){_pRoot = new Node(data);return true;}Node* parent = nullptr;Node* cur = _pRoot;while (cur){if (data > cur->_data){parent = cur;cur = cur->_pRight;}else if (data < cur->_data){parent = cur;cur = cur->_pLeft;}else{return false;}}cur = new Node(data);if (parent->_data < data){parent->_pRight = cur;}else{parent->_pLeft = cur;}cur->_pParent = parent;//調整平衡因子while (parent){if (cur == parent->_pLeft){parent->_bf--;}else{parent->_bf++;}//插入后恰好平衡了,跳出循環if (parent->_bf == 0){break;}    //高度出現變化但還未失衡,向上走進行判斷else if (parent->_bf == 1 || parent->_bf == -1){cur = parent;parent = parent->_pParent;}	//已經失衡需要進行調整else if (parent->_bf == 2 || parent->_bf == -2){if (parent->_bf == -2 && cur->_bf == -1){RotateR(parent);}else if (parent->_bf == 2 && cur->_bf == 1){RotateL(parent);}else if (parent->_bf == 2 && cur->_bf == -1){RotateRL(parent);}else if (parent->_bf == -2 && cur->_bf == 1){RotateLR(parent);}break;}else{// 理論而言不可能出現這個情況assert(false);}}return true;}// AVL樹的驗證bool IsAVLTree(){return _IsAVLTree(_pRoot);}int Size(){return _Size(_pRoot);}void InOrder(){_InOrder(_pRoot);cout << endl;}size_t Height(){return _Height(_pRoot);}private:void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_pLeft);cout << root->_data << endl;_InOrder(root->_pRight);}bool _IsAVLTree(Node* root){if (root == nullptr)return true;int leftHeight = _Height(root->_pLeft);int rightHeight = _Height(root->_pRight);// 不平衡if (abs(leftHeight - rightHeight) >= 2){cout << root->_data << endl;return false;}// 順便檢查一下平衡因子是否正確if (rightHeight - leftHeight != root->_bf){cout << root->_data << endl;return false;}return _IsAVLTree(root->_pLeft)&& _IsAVLTree(root->_pRight);}size_t _Height(Node* _pRoot){if (_pRoot == nullptr) return 0;return max(_Height(_pRoot->_pLeft), _Height(_pRoot->_pRight)) + 1;}int _Size(Node* root){return root == nullptr ? 0 : _Size(root->_pLeft) + _Size(root->_pRight) + 1;}// 右單旋void RotateR(Node* pParent){Node* subL = pParent->_pLeft;Node* subLR = subL->_pRight;pParent->_pLeft = subLR;if (subLR)subLR->_pParent = pParent;subL->_pRight = pParent;Node* temp = pParent->_pParent;pParent->_pParent = subL;//pParent有可能為根,右單旋后應該更新根節點指向。if (pParent == _pRoot){_pRoot = subL;_pRoot->_pParent = nullptr;}else{if (temp->_pLeft == pParent){temp->_pLeft = subL;}else{temp->_pRight = subL;}subL->_pParent = temp;}pParent->_bf = subL->_bf = 0;}// 左單旋void RotateL(Node* pParent){Node* subR = pParent->_pRight;Node* subRL = subR->_pLeft;pParent->_pRight = subRL;if (subRL)subRL->_pParent = pParent;subR->_pLeft = pParent;Node* temp = pParent->_pParent;pParent->_pParent = subR;//pParent有可能為根,右單旋后應該更新根節點指向。if (pParent == _pRoot){_pRoot = subR;_pRoot->_pParent = nullptr;}else{if (temp->_pLeft == pParent){temp->_pLeft = subR;}else{temp->_pRight = subR;}subR->_pParent = temp;}pParent->_bf = subR->_bf = 0;}// 右左雙旋void RotateRL(Node* pParent){Node* subR = pParent->_pRight;Node* subRL = subR->_pLeft;int bf = subRL->_bf;RotateR(subR);RotateL(pParent);subRL->_bf = 0;if (bf == 1){pParent->_bf = -1;subR->_bf = 0;}else if (bf == -1){pParent->_bf = 0;subR->_bf = 1;}else{pParent->_bf = 0;subR->_bf = 0;}}// 左右雙旋void RotateLR(Node* pParent){Node* subL = pParent->_pLeft;Node* subLR = subL->_pRight;int bf = subLR->_bf;RotateL(subL);RotateR(pParent);subLR->_bf = 0;if (bf == 1){pParent->_bf = 0;subL->_bf = -1;}else if (bf == -1){pParent->_bf = 1;subL->_bf = 0;}else{pParent->_bf = 0;subL->_bf = 0;}}private:Node* _pRoot;};
}

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

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

相關文章

摸魚大數據——Spark基礎——Spark環境安裝——Spark Local[*]搭建

一、虛擬機配置 查看每一臺的虛擬機的IP地址和網關地址 查看路徑: cat /etc/sysconfig/network-scripts/ifcfg-ens33 2.修改 VMware的網絡地址: 使用VMnet8 3.修改windows的對應VMware的網卡地址 4.通過finalshell 或者其他的shell連接工具即可連接使用即可, 連接后, 測試一…

如何在Java中實現事件驅動編程?

如何在Java中實現事件驅動編程&#xff1f; 大家好&#xff0c;我是免費搭建查券返利機器人省錢賺傭金就用微賺淘客系統3.0的小編&#xff0c;也是冬天不穿秋褲&#xff0c;天冷也要風度的程序猿&#xff01;今天我們將探討如何在Java中實現事件驅動編程&#xff0c;這是一種強…

AD PCB板子裁剪與淚滴設置

在剪裁板子時。首先&#xff0c;選擇選擇板子的機械層&#xff0c;之后選擇畫線。在原來的板子上畫上自己想要裁剪的圖形。如下下圖 之后&#xff0c;選擇按照所畫的線裁剪板子即可&#xff0c;如下 在焊接PCB時&#xff0c;為了防止多次焊接導至焊盤脫落可以加大焊點的接觸面積…

ESP32-C3模組上跑通MQTT(6)—— tcp例程(1)

接前一篇文章:ESP32-C3模組上跑通MQTT(5) 《ESP32-C3 物聯網工程開發實戰》 一分鐘了解MQTT協議 ESP32 MQTT API指南-CSDN博客 ESP-IDF MQTT 示例入門_mqtt outbox-CSDN博客 ESP32用自簽CA進行MQTT的TLS雙向認證通信_esp32 mqtt ssl-CSDN博客 特此致謝! 本回開始正式講…

mac docker 運行mysql5.7 鏡像失敗解決

12312 qemu: uncaught target signal 11 (Segmentation fault) InnoDB: Linux Native AIO interface is not supported on this platform. Please check your OS documentation and install appropriate binary of InnoDB. 問題如上 一般來說&#xff0c;拉取mysql8是沒問題…

淺談css的cusor屬性

在網頁設計中&#xff0c;細節決定成敗。CSS的cursor屬性是這些細節中的關鍵一環&#xff0c;它不僅影響著網頁的美觀&#xff0c;更關乎用戶體驗。今天&#xff0c;我們就來深入了解一下cursor屬性&#xff0c;看看如何通過它來增強網頁的交互性。 cursor屬性概覽 cursor屬性…

華潤萬家超市卡怎么用?

華潤的禮品卡不僅能線下門店使用&#xff0c;還能直接叫送貨上門 我最近用積分兌了幾張華潤卡&#xff0c;但是又沒有購物需求&#xff0c;送朋友吧面值又不大&#xff0c;朋友也說用不上 最后朋友建議我在收卡云上把卡出掉&#xff0c;我試了下92折出掉了&#xff0c;價格還…

代碼隨想錄算法訓練營第四十七天| 188.買賣股票的最佳時機IV ,309.最佳買賣股票時機含冷凍期 ,714.買賣股票的最佳時機含手續費

188. 買賣股票的最佳時機 IV - 力扣&#xff08;LeetCode&#xff09; class Solution {public int maxProfit(int k, int[] prices) {int[][] dp new int[prices.length][2*k];for(int i0;i<2*k;i){if(i%2 0){dp[0][i] -prices[0];}else{dp[0][i] 0;} }for(int i1;i…

綜合項目實戰--jenkins節點模式

一、DevOps流程 DevOps是一種方法論,是一系列可以幫助開發者和運維人員在實現各自目標的前提下,向自己的客戶或用戶交付最大化價值及最高質量成果的基本原則和實踐,能讓開發、測試、運維效率協同工作的方法。 DevOps流程(自動化測試部分) DevOps完整流程 二、gitee+j…

內網和外網的區別及應用

內網和外網的區別及應用 大家好&#xff0c;我是免費搭建查券返利機器人省錢賺傭金就用微賺淘客系統3.0的小編&#xff0c;也是冬天不穿秋褲&#xff0c;天冷也要風度的程序猿&#xff01;今天我們來探討一下計算機網絡中的內網和外網&#xff0c;它們的區別以及在實際應用中的…

go sync包(四) 讀寫鎖(二)

讀寫鎖 RWMutex 寫鎖 加鎖 RWMetex 的寫鎖復用了 Mutex&#xff1a; // Lock locks rw for writing. // If the lock is already locked for reading or writing, // Lock blocks until the lock is available. func (rw *RWMutex) Lock() {if race.Enabled {_ rw.w.state…

安全與發展并重:實施等保,促進企業可持續增長的邏輯

在數字經濟時代&#xff0c;信息安全不僅是企業穩健運營的基石&#xff0c;也是推動可持續發展的重要保障。網絡安全等級保護&#xff08;簡稱“等保”&#xff09;體系&#xff0c;作為國家層面設立的信息安全保障框架&#xff0c;其核心在于平衡安全與發展的關系&#xff0c;…

Java中如何進行分布式系統設計?

Java中如何進行分布式系統設計&#xff1f; 大家好&#xff0c;我是免費搭建查券返利機器人省錢賺傭金就用微賺淘客系統3.0的小編&#xff0c;也是冬天不穿秋褲&#xff0c;天冷也要風度的程序猿&#xff01;今天&#xff0c;我們來討論如何在Java中進行分布式系統設計。分布式…

什么是 Python 包管理器?怎么安裝?

Python 包管理器是一個用于安裝、升級、卸載和管理 Python 包的工具。Python 的包&#xff08;也稱為模塊或庫&#xff09;是預編寫的 Python 代碼&#xff0c;用于執行各種任務&#xff0c;如數據處理、網頁開發、科學計算等。Python 包管理器使得這些包的管理變得簡單和高效。…

Android Gradle開發與應用 (第一部分):入門Gradle基礎

Gradle 是一個開源的構建自動化工具&#xff0c;廣泛用于Android項目的構建和管理。本文將介紹Gradle的基礎知識&#xff0c;幫助開發者更好地理解和使用Gradle進行Android應用開發。 目錄 什么是GradleGradle的基本概念配置Gradle環境Gradle構建腳本結構常用Gradle命令多項目…

計算Dice損失的函數

計算Dice損失的函數 def Dice_loss(inputs, target, beta1, smooth 1e-5):n,c, h, w inputs.size() #nt,ht, wt, ct target.size() #nt,if h ! ht and w ! wt:inputs F.interpolate(inputs, size(ht, wt), mode"bilinear", align_cornersTrue)temp_inputs t…

LLaMA-Factory安裝

安裝代碼 https://github.com/echonoshy/cgft-llm/blob/master/llama-factory/README.md https://github.com/hiyouga/LLaMA-Factory/tree/mainLLaMA-Factoryhttps://github.com/hiyouga/LLaMA-Factory/tree/main 【大模型微調】- 使用Llama Factory實現中文llama3微調_嗶哩…

TIA博途WinCC通過VB腳本從 Excel中讀取數據的具體方法介紹

TIA博途WinCC通過VB腳本從 Excel中讀取數據的具體方法介紹 添加 一個PLC,設置PLC的IP地址,如下圖所示, 添加全局DB塊,新建幾個變量,如下圖所示, 在數據塊中添加了 tag1 …… tag6 ,共 6 個浮點數類型的變量,用來接收通過 WinCC 從 Excel 文件中讀取的數據。 添加 HMI…

Holt-Winters季節性方法

Holt-Winters季節性方法是時間序列預測中一種常用的方法&#xff0c;它通過三次指數平滑處理數據中的趨勢和季節性成分。下面將詳細解釋該方法的原理和步驟&#xff1a; 1. 數據準備 數據收集與整理&#xff1a;首先需要收集和整理時間序列數據&#xff0c;確保數據的準確性和…

什么是pip命令

pip 是 Python 的包管理器&#xff0c;用于安裝和管理 Python 包&#xff08;也稱為模塊或庫&#xff09;。Python 包是預編寫的 Python 代碼&#xff0c;用于執行特定任務&#xff0c;如數據處理、網頁開發、科學計算等。通過使用 pip&#xff0c;您可以輕松地安裝、升級或卸載…