平衡樹的模擬實現

一.平衡樹的介紹

平衡樹是以二叉樹結構為基礎,同時引入了平衡因子進行了限制,以保證樹的結點之間的高度差小于等于1,在插入刪除結點時通過旋轉的方法保持高度相對平衡,從而提高搜索等效率。

二.代碼實現

1.平衡樹結點

平衡樹結點是以二叉樹為基礎構建的,因此需要左右結點指針left與right。傳入pair一部分為值value另一部分為關鍵字key,以及平衡因子bf(左子樹高度減右子樹高度)。

下面是結點代碼:

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

?2.平衡樹的插入

插入結點時首先要找到合適的位置空間來放置新結點,所以我們需要遍歷樹。從根結點開始(cur),若值比當前結點小讓cur往左邊走,若值比當前結點大讓cur往右邊走,直到cur跑到空時停止。

接著進行判斷,根據結點高度差的不同,結點位置不一樣進行不同的旋轉方式,調整相應的平衡因子,根據parent結點的高度決定是否要繼續向上更新。

下面是尋找結點的相應代碼:

if (_root == nullptr){_root = new Node(kv);return true;}Node* parent = nullptr;Node* cur = _root;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;}}cur = new Node(kv);if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;

若樹的結點為空那么直接插入根結點,若不為空按照上述的方法遍歷到空結點后,需要注意要確定parent的位置。

?下面我們來討論一下旋轉

首先計算平衡因子,若cur在parent的左邊則bf--,若cur在parent的右邊則bf++,若parent的bf為0時直接跳出結果。

若parent的左右都存在結點,那么就是平衡的,反之則不平衡,若bf等于1或者-1,時繼續向上更新bf平衡值,讓cur等于parent,parent等于parent的parent。

向上遍歷若得到bf等于2或者-2時,說明此時樹已經不平衡了,需要進行旋轉調整樹的結構。

下面我們依次分析左旋,右旋,左右雙旋,右左雙旋的情況

1.右單旋

當parent的平衡因子為-2,cur的平衡因子為-1時我們需要右單旋

如圖p代表parent,c代表cur此時我們需要進行右旋操作d代表插入的結點。以parent為軸向右旋轉讓cur變成parent。

我們需要得到parent結點,cur結點(SUL),以及cur結點的右結點(SULR)。

讓SULR變成parent的左結點SUL的右結點變成parent,其他的不進行改變。

需要注意的是,首先需要判斷SULR結點是否存在,若存在就讓其變為parent的左結點,若不存在就不進行操作。以及我們需要找到parent結點的parent結點,若parentparent結點為空則則讓root結點直接變為SUL即可,若不為空需要判斷parent結點是parentparent結點的左還是右結點,然后將其左或右給給SUL。

下面放上代碼:

void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* parentParent = parent->_parent;subL->_right = parent;parent->_parent = subL;//parent有可能是整棵樹的根,也有可能是局部子樹//如果是整棵樹的根則修改root//if (parentParent == nullptr){_root = subL;subL->_parent = nullptr;}else{if (parent == parentParent->_left){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent;}parent->_bf = subL->_bf = 0;}

2.左單旋?

左單旋與右單旋類似,只是parent結點的左右子樹互換了位置。

當parent的bf為2時,cur的bf為1時,需要進行左旋操作。

如圖p代表parent ,c代表cur結點,插入的結點為a。以parent為軸向左旋轉。

我們需要得到cur結點的左結點(SURL),parent結點以及cur結點(SUR)。讓parent的右為SURL,SUR的左為parent。其他的不進行改變。?

與上文相同需要找到parent的parent,若parentparent不存在就將root結點設置為SUR,若存在,則根據parent是parentparent的左或者右結點來分配給SUR結點。

下面是代碼:

void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;Node* parentParent = parent->_parent;subR->_left = parent;parent->_parent = subR;if (parentParent == nullptr){_root = subR;subR->_parent = nullptr;}else{if (parent == parentParent->_left){parentParent->_left = subR;}else{parentParent->_right = subR;}subR->_parent = parentParent;}parent->_bf = subR->_bf = 0;}

3.左右雙旋?

當parent的bf等于-2,cur的bf等于1時,進行左右雙旋。先對cur進行左旋讓其bf等于-1,此時就回到了第一種情況,再對parent進行一次右旋就完成了操作。

當新增結點cur結點與parent結點形成一個角度時,需要進行雙旋操作,如果是開口向右則進行左右雙旋,若開口向左則進行右左雙旋。

此處的難點在于對插入結點bf的分類討論。

第一種情況 當SULR(R)的bf為0時

?此時我們需要先對SUL進行左旋操作,使其滿足三個點連成一條直線,之后再對parent進行右旋操作,得到下圖。

我們可以根據最終結果直接寫出代碼,先對SUL進行左旋再對parent進行右旋,最后設置三個結點的bf都為0即可。

第二種情況 當SULR(R) 的bf為-1時

當我們進行左右雙旋操作后

變成如圖,此時需要將parent的bf設置為1,SUL和SULR設置為0。

第三種情況 當SURL(R)的bf為1時

進行左右雙旋操作之后

?將SUL的bf設置為-1,parent與SULR的bf設置為0即可。

下面附上代碼:

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

4.右左雙旋

右左雙旋本質與左右雙旋一樣,只是左右子樹位置互換

所以我就直接附上代碼:

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

?5.插入代碼總結

bool insert(const pair<K, V>& kv){if (_root == nullptr){_root = new Node(kv);return true;}Node* parent = nullptr;Node* cur = _root;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;}}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--;elseparent->_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)//左單旋{RotateL(parent);}else if (parent->_bf == 2 && cur->_bf == -1)//右左雙旋{RotateRL(parent);}else if (parent->_bf == -2 && cur->_bf == 1)//左右雙旋{RotateLR(parent);}else if(parent->_bf == -2 && cur->_bf == -1)//右單旋{RotateR(parent);}break;}else{//說明傳入的有問題報錯日志assert(false);}}return true;}//右單旋void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* parentParent = parent->_parent;subL->_right = parent;parent->_parent = subL;//parent有可能是整棵樹的根,也有可能是局部子樹//如果是整棵樹的根則修改root//if (parentParent == nullptr){_root = subL;subL->_parent = nullptr;}else{if (parent == parentParent->_left){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent;}parent->_bf = subL->_bf = 0;}//左單旋void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;Node* parentParent = parent->_parent;subR->_left = parent;parent->_parent = subR;if (parentParent == nullptr){_root = subR;subR->_parent = nullptr;}else{if (parent == parentParent->_left){parentParent->_left = subR;}else{parentParent->_right = subR;}subR->_parent = parentParent;}parent->_bf = subR->_bf = 0;}void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent);if (bf == 0){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 0;}else if (bf == 1){subL->_bf = -1;parent->_bf = 0;subLR->_bf = 0;}else if (bf == -1){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 1;}else{assert(false);}}void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(parent->_right);RotateL(parent);if (bf == 0){subR->_bf = 0;subRL->_bf = 0;parent->_bf = 0;}else if (bf == 1){subRL->_bf = 0;parent->_bf = -1;subR->_bf = 0;}else if (bf == -1){subLR->_bf = 0;subR->_bf = 1;parent->_bf = 0;}else{assert(false);}}

3.鍵值查找 ?樹的高度計算 與平衡樹判斷

查找值時,只需要遍歷整個樹即可,以根節點(cur)為起始點,當所找的值比cur小時向左樹尋找,比cur大時向右樹尋找,直到cur值等于尋找值時返回當前結點,若cur為空了仍未找到結點就返回空。

Node* find(const K& key){Node* cur = _root;while (cur){if (cur->_kv.first < key){cur = cur->_right;}else if (cur->_kv.first > key){cur = cur->_left;}else{return cur;}}return nullptr;}

計算樹的高度時我們采用遞歸左右子樹的方法實現,判斷左右子樹的大小取大值,逐層遞歸得到最終值。

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

?判斷是否平衡時,我們可以調用左右子樹的高度,然后相減,得到diff。若diff大于2說明高度異常,若根結點的bf不等于diff說明平衡因子計算存在錯誤。

bool _IsBalanceTree(Node* root){if (nullptr == root)return true;int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);int diff = leftHeight - rightHeight;if (abs(diff) >= 2){cout << root->_kv.first << "高度異常" << endl;}if (root->_bf != diff){cout << root->_kv.first << "平衡因子異常" << endl;return false;}return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);}

?三.總結

平衡樹要求任意節點的左右子樹高度差絕對值不超過 1,且左右子樹本身也是平衡樹。通過限制樹的高度(保持在?O(logn)?級別),確保查找、插入、刪除等操作的時間復雜度穩定在?O(logn),避免退化為鏈表的?O(n)?復雜度。每次插入或刪除后通過旋轉(左旋、右旋、左右雙旋、右左雙旋)立即恢復平衡。

它的優點在于時間復雜度低,適合大量數據的操作,缺點在于需要動態的更新數據進行旋轉增大了時間開銷。

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

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

相關文章

JavaScript基礎-獲取元素

在Web開發中&#xff0c;使用JavaScript動態地訪問和操作網頁上的元素是一項基本技能。通過獲取頁面上的特定元素&#xff0c;我們可以對其進行各種操作&#xff0c;比如修改內容、樣式或屬性等。本文將詳細介紹幾種獲取DOM元素的方法&#xff0c;并探討它們的特點及適用場景。…

為什么要用(:deep、::v-deep、>>>)樣式穿透

在 Vue.js 中&#xff0c;當你使用像 Element UI 這樣的 UI 庫時&#xff0c;它們的樣式通常是全局的&#xff0c;即使你在組件中使用了 scoped 樣式&#xff08;為什么要用scoped&#xff09;&#xff0c;仍然可能需要對這些全局樣式進行修改。 為了實現這一點&#xff0c;樣…

MySQL中的事務隔離級別有哪些

MySQL中的事務隔離級別 一、事務并發問題二、MySQL 事務隔離級別1. READ UNCOMMITTED&#xff08;讀未提交&#xff09;2. READ COMMITTED&#xff08;讀已提交&#xff09;3. REPEATABLE READ&#xff08;可重復讀&#xff09;&#xff08;MySQL 默認級別&#xff09;4. SERIA…

Python----計算機視覺處理(Opencv:圖像鏡像旋轉)

一、圖像鏡像旋轉 圖像的旋轉是圍繞一個特定點進行的&#xff0c;而圖像的鏡像旋轉則是圍繞坐標軸進行的。圖像鏡像旋轉&#xff0c;也可 以叫做圖像翻轉&#xff0c;分為水平翻轉、垂直翻轉、水平垂直翻轉三種。 通俗的理解為&#xff0c;當以圖片的中垂線為x軸和y軸時&#x…

hibernate 自動生成數據庫表和java類 字段順序不一致 這導致添加數據庫數據時 異常

hibernate 自動生成的數據庫表和java類 字段順序不一致 這導致該書寫方式添加數據庫數據時 異常 User user new User( null, username, email, phone, passwordEncoder.encode(password) ); return userRepository.save(user);Hibernate 默認不會保證數據庫表字段的順序與 Ja…

python|結構的模式匹配match|同步迭代

在 Python 中&#xff0c;模式匹配&#xff08;Pattern Matching&#xff09; 是一種強大的功能&#xff0c;用于根據數據的結構或內容進行匹配和處理。Python 3.10 引入了 match 語句&#xff0c;使得模式匹配更加直觀和靈活。模式匹配可以用于處理復雜的數據結構&#xff0c;…

博客圖床 VsCode + PigGo + 阿里云OSS

關鍵字 寫博客&#xff0c;圖床&#xff0c;VsCode&#xff0c;PigGo&#xff0c;阿里云OSS 背景環境 我想把我在本地寫的markdown文檔直接搬到CSDN上和博客園上&#xff0c;但是圖片上傳遇到了問題。我需要手動到不同平臺上傳文件&#xff0c;非常耗費時間和經歷。 為了解決…

路由器安全研究:D-Link DIR-823G v1.02 B05 復現與利用思路

前言 D-Link DIR-823G v1.02 B05存在命令注入漏洞&#xff0c;攻擊者可以通過POST的方式往 /HNAP1發送精心構造的請求&#xff0c;執行任意的操作系統命令。 漏洞分析 binwalk提取固件&#xff0c;成功獲取到固件。 現在我們已經進入到應用里了&#xff0c;那么我們在進行分析…

c++ 類和對象 —— 下 【復習總結】

1. 深入構造函數 1.1 函數體賦值 前文我們提到&#xff0c;創建對象時&#xff0c;編譯器會調用構造函數給成員變量賦值。但這并不能稱為對對象中成員變量的初始化。因為初始化只能初始化一次&#xff0c;但構造函數體內可以多次賦值。構造函數體中語句只能稱為賦初值 那么&…

【量化科普】Volatility,波動率

【量化科普】Volatility&#xff0c;波動率 &#x1f680;量化軟件開通 &#x1f680;量化實戰教程 在金融市場中&#xff0c;波動率&#xff08;Volatility&#xff09;是衡量資產價格變動幅度的一個重要指標。它反映了資產價格的穩定性和風險水平。高波動率意味著資產價格…

PCIe(Peripheral Component Interconnect Express)詳解

一、PCIe的定義與核心特性 PCIe&#xff08;外設組件互連高速總線&#xff09;是一種 高速串行點對點通信協議&#xff0c;用于連接計算機內部的高性能外設。它取代了傳統的PCI、PCI-X和AGP總線&#xff0c;憑借其高帶寬、低延遲和可擴展性&#xff0c;成為現代計算機系統的核…

idea 編譯打包nacos2.0.3源碼,生成可執行jar 包常見問題

目錄 問題1 問題2 問題3 問題4 簡單記錄一下nacos2.0.3&#xff0c;編譯打包的步驟&#xff0c;首先下載源碼&#xff0c;免積分下載&#xff1a; nacos源碼&#xff1a; https://download.csdn.net/download/fyihdg/90461118 protoc 安裝包 https://download.csdn.net…

通過 TTL 識別操作系統的原理詳解

TTL 的工作原理 TTL&#xff08;Time to Live&#xff0c;生存時間&#xff09;是網絡中用于控制數據包生命周期的一個關鍵參數。它通過限制數據包在網絡中可以經過的最大路由跳數&#xff08;或最大轉發時間&#xff09;&#xff0c;確保數據包不會在網絡中無休止地轉發。TTL…

總結Solidity 的數據類型

數據類型 在 Solidity 中&#xff0c;類型系統非常豐富&#xff0c;主要分為 值類型&#xff08;Value Types&#xff09;和 引用類型&#xff08;Reference Types&#xff09;。此外&#xff0c;還有一些特殊類型和全局變量。 一.值類型 布爾型&#xff08;bool&#xff09…

Android audio(8)-native音頻服務的啟動與協作(audiopolicyservice和audioflinger)

音頻策略的構建 1、概述 2、AudiopolicyService 2.1 任務 2.2 啟動流程 2.2.1 加載audio_policy.conf&#xff08;xml&#xff09;配置文件 2.2.2 初始化各種音頻流對應的音量調節點 2.2.3 加載audio policy硬件抽象庫 2.2.4設置輸出設備 ps:audiopatch流程簡介 2.2.5打開輸出設…

DeepSeek:從入門到精通

DeepSeek是什么&#xff1f; DeepSeek是一家專注通用人工智能&#xff08;AGI&#xff09;的中國科技公司&#xff0c;主攻大模型研發與應 用。DeepSeek-R1是其開源的推理模型&#xff0c;擅長處理復雜任務且可免費商用。 Deepseek可以做什么&#xff1f; 直接面向用戶或者支持…

【一起來學kubernetes】17、Configmap使用詳解

前言概述核心特性創建 ConfigMap使用 ConfigMap1. **環境變量**2. **Volume 掛載**3. **命令行參數** 更新與熱重載Docker容器中Java服務使用Configmap**一、通過環境變量注入****步驟說明****示例配置** **二、通過 Volume 掛載配置文件****步驟說明****示例配置** **三、動態…

【八股文】從瀏覽器輸入一個url到服務器的流程

1.url解析與DNS解析 瀏覽器解析用戶輸入的URL&#xff0c;提取協議&#xff08;HTTP\HTTPS&#xff09;、域名、端口及路徑等信息 瀏覽器首先檢查本地DNS緩存和系統DNS緩存&#xff0c;若未命中&#xff0c;查詢本地hosts文件 最后遞歸查詢向本地DNS服務器發起請求&#xff…

網絡空間安全(34)安全防御體系

前言 安全防御體系是一個多層次、多維度的系統&#xff0c;旨在保護組織或個人的信息資產免受各種網絡攻擊和威脅。 一、技術層面 網絡邊界防御 防火墻&#xff1a;部署在網絡邊界&#xff0c;通過設定規則允許或阻止特定流量的進出&#xff0c;保護內部網絡不受外部攻擊。入侵…

Linux 入門:權限的認識和學習

目錄 一.shell命令以及運行原理 二.Linux權限的概念 1.Linux下兩種用戶 cannot open directory .: Permission denied 問題 2.Linux權限管理 1).是什么 2).為什么&#xff08;權限角色目標權限屬性&#xff09; 3).文件訪問者的分類&#xff08;角色&#xff09; 4).文…