C++_AVL樹

? ? ? ?

目錄

1、AVL的概念

2、平衡因子的調整概念

3、AVL樹的插入

3.1 調整平衡因子代碼實現

3.2 右旋操作

3.2 左旋操作?

3.3 雙旋-先右旋再左旋

3.4?雙旋-先左旋再右旋

3.5 旋轉操作的小結

4、AVL的驗證與實現

結語


前言:

????????在C++中,AVL樹是在二叉搜索樹的基礎上優化而來的,因為二叉搜索樹有一個缺陷,即如果插入的數據接近有序或者有序,那么二叉搜索樹就會變成一邊倒的結構,也就是”單支樹“,而”單支樹“查找數據效率就會變成和線性數據結構一樣的O(N),失去了二叉樹本有的優勢。 單支樹示意圖如下:

????????因此兩位俄羅斯的數學家G.M.Adelson-Velskii 和E.M.Landis1962推出了AVL樹的概念,AVL樹在每一次插入新節點的同時,會自動調整該樹的高度,即不會讓每個節點的左右子樹高度的絕對值不超過1,因此哪怕面對有序數據的插入也不會讓樹變成”單支樹“,可以使查找數據的效率始終保持在O(log N)。

1、AVL的概念

? ? ? ? AVL樹滿足以下兩個條件:

? ? ? ? 1、其每顆子樹都是AVL樹。

? ? ? ? 2、每個節點的左右子樹高度(可在節點中新加一個變量-平衡因子表示該節點的左右子樹高度)的絕對值不超過1。

? ? ? ? AVL樹示意圖如下:

? ? ? ? 其中,-1表示該節點的左子樹高度高于右子樹高度1個單位,0表示該節點左右子樹高度一樣,1表示該節點的右子樹高度高于左子樹高度1個單位。轉換成公式:平衡因子=右子樹高度-左子樹高度

2、平衡因子的調整概念

? ? ? ? AVL樹插入新節點的規則依舊是按照二叉搜索樹的規則,即左節點小于根結點,而右節點大于跟節點。只不過AVL新加入了平衡因子的概念,所以每次插入節點后,都需要對平衡因子做出調整,比如插入的節點為該子樹的根結點的右邊,則根結點的平衡因子需要+1。

? ? ? ? 此時,平衡因子的狀態就會出現三種情況:

? ? ? ? 1、插入節點后,根結點的平衡因子從正負1變為0,這種情況說明插入該節點后這棵樹子樹得到了平衡,無需做任何處理。

? ? ? ? 2、插入節點后,根結點的平衡因子從0變為1或-1,這種情況說明插入的節點破壞了該樹的原有平衡,雖然當前子樹的根結點的平衡因子的絕對值沒有超過1,但是該子樹的祖先節點的平衡因子的絕對值可能已經超過1了,所以需要”向上更新“,更改祖先節點的平衡因子。

? ? ? ? 3、插入節點后,該樹的某個節點的平衡因子的絕對值超過了1,則需要對該子樹進行旋轉處理。

? ? ? ? 具體示意圖如下:

? ? ? ? 此時會發現,不管新插入的節點在9的右邊還是左邊,都會導致節點8的平衡因子變成2,因為對于9來說可能在哪邊插入節點都行,但是對于節點8來說,這兩個插入的節點都在8的右子樹,所以會導致8的平衡因子變成2。

? ? ? ? 在節點9的左邊插入節點的情況如下:


? ? ? ? ?只有在8的左邊插入節點,才不會引發旋轉調整,示意圖如下:

3、AVL樹的插入

? ? ? ? AVL樹的插入函數的實現可以分成三步:1、找到插入節點的合適位置。2、調整節點的平衡因子。3、對平衡因子異常的節點進行旋轉操作。

? ? ? ? 第一步是復用了二叉搜索樹的插入邏輯,即判斷要插入節點的值是否大于或小于根結點,若大于根結點則往右子樹遍歷,若小于根結點則往左子樹遍歷,直到找到節點指向的nullptr處,然后將插入節點與其父母節點相連接。

? ? ? ? 第二步調整平衡因子邏輯即上文敘述。

? ? ? ? 第三步進行的旋轉操作有:左旋、右旋、雙旋轉(即左右旋的結合),具體如下文。

3.1 調整平衡因子代碼實現

? ? ? ? 將上述的調整場景轉換為代碼:

#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>
using namespace std;template<class K, class V>
class AVLTreeNode//節點
{AVLTreeNode<K, V>* _left;//指向左節點AVLTreeNode<K, V>* _right;//指向右節點AVLTreeNode<K, V>* _parent;//指向自己的父母節點pair<K, V> _kv;// 記錄數據用的pair類型int _bf; // 平衡因子AVLTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _bf(0){}
};template<class K, class V>
class AVLTree//AVL樹
{typedef AVLTreeNode<K, V> Node;
public: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;//AVL和二叉搜索樹一樣不允許有相同數據}}cur = new Node(kv);//找到合適位置后插入節點if (parent->_kv.first > kv.first){parent->_left = cur;//將該節點與父母節點相連接}else{parent->_right = cur;//將該節點與父母節點相連接}cur->_parent = parent;//將該節點與父母節點相連接// 更新平衡因子while (parent){if (cur == parent->_right){parent->_bf++;//插入的位置是節點的右邊,則平衡因子++}else{parent->_bf--;//插入的位置是節點的左邊,則平衡因子--}if (parent->_bf == 1 || parent->_bf == -1){// 繼續更新parent = parent->_parent;cur = cur->_parent;}else if (parent->_bf == 0)//等于0說明平衡了,則不處理{break;}else if (parent->_bf == 2 || parent->_bf == -2){// 需要旋轉處理 -- 1、讓這顆子樹平衡 2、降低這顆子樹的高度}else{assert(false);}}return true;}private:Node* _root = nullptr;
};

? ? ? ? 以上代碼已經實現了合適位置的查找和平衡因子的調整,插入函數的實現還差最后一步,即平衡因子等于2或者-2時旋轉操作的實現。

3.2 右旋操作

? ? ? ? 右旋操作是針對平衡因子為-2的場景,說明該節點的左子樹比右子樹要高,需要右旋降低左子樹的高度。并且旋轉完成后要更新平衡因子,具體操作示意圖如下:

? ? ? ? 從結果可以看到,原本平衡因子是-2的節點經過旋轉操作之后變成了0。并且旋轉之后的節點與節點之間的邏輯依然是遵循小于根結點的在左邊,大于根節點的在右邊。

3.2 左旋操作?

? ? ? ? 左旋操作是針對平衡因子為2的場景,說明該節點的右子樹比左子樹要高,需要左旋降低右子樹的高度。并且旋轉完成后要更新平衡因子,具體操作示意圖如下:

?????????從結果可以看到,原本平衡因子是2的節點經過旋轉操作之后變成了0。

? ? ? ? 無論是左旋還是右旋,都要注意兩個事項:

? ? ? ? 一、拿上述左旋舉例,節點12不一定存在左孩子,如果不存在左孩子則10的平衡因子是-1。

? ? ? ? 二、節點10不一定是根結點,可能是某個節點的左子樹或者右子樹,若10只是子樹,則旋轉后要將節點12與原先節點10的父母節點相連接。

3.3 雙旋-先右旋再左旋

? ? ? ? 從上述的例子可以發現,插入的節點都處于”邊緣“節點下,比如拿上述的左旋舉例,若插入的節點是插入到節點11下則如何調整呢?這時候就要用到雙選調整,比如上述的節點14作為節點11的孩子插入,則需要以節點12為一棵子樹,先進行右旋操作,然后再以根結點10進行左旋操作。

????????先右旋再左旋示意圖如下:

3.4?雙旋-先左旋再右旋

?????????用上述右旋的例子來進行說明,如果插入的節點是作為節點8的孩子節點,則需要先以節點6為子樹進行左旋,然后再以根結點10進行整棵樹的右旋。

? ? ? ? 先左旋再右旋的示意圖如下:

3.5 旋轉操作的小結

經過上述旋轉操作的細述,大致可以得出以下結論:

一、平衡因子為2的節點,說明該節點的右子樹高,因此只需要關注該節點的右孩子,其右孩子的平衡因子會有兩種情況:

? ? ? ? 1、該節點平衡因子若為1則只需要進行左旋轉即可。

? ? ? ? 2、該節點平衡因子若為-1則需要進行雙旋-先右旋再左旋。

二、平衡因子為-2的節點,說明該節點的左子樹高,因此只需要關注該節點的左孩子,其左孩子的平衡因子會有兩種情況:

? ? ? ? 1、該節點平衡因子若為1則需要進行雙旋-先左旋再右旋。

? ? ? ? 2、該節點平衡因子若為-1則只需要進行右旋轉即可。

? ? ? ? 從以上結論可以寫出旋轉代碼。

4、AVL的驗證與實現

? ? ? ? 判斷一棵樹是否為AVL樹的依據有兩點:1、中序遍歷的方式打印出來的是有序數據。2、每個節點的平衡因子的絕對值不超過2。

? ? ? ? AVL樹的實現代碼如下:?

#define _CRT_SECURE_NO_WARNINGS 1
#include <assert.h>
#include <time.h>
#include<iostream>
using namespace std;template<class K, class V>
struct AVLTreeNode
{AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;pair<K, V> _kv;int _bf; // balance factorAVLTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _bf(0){}
};template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public: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->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;// 更新平衡因子while (parent){if (cur == parent->_right){parent->_bf++;}else{parent->_bf--;}if (parent->_bf == 1 || parent->_bf == -1){parent = parent->_parent;cur = cur->_parent;}else if (parent->_bf == 0){break;}else if (parent->_bf == 2 || parent->_bf == -2){// 旋轉處理:1、讓這顆子樹平衡 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;}void InOrder(){_InOrder(_root);cout << endl;}bool IsBalance(){return _IsBalance(_root);}int Height(){return _Height(_root);}private:int _Height(Node* root){if (root == NULL)return 0;int leftH = _Height(root->_left);int rightH = _Height(root->_right);return leftH > rightH ? leftH + 1 : rightH + 1;}bool _IsBalance(Node* root)//觀察平衡因子是否有問題{if (root == NULL){return true;}int leftH = _Height(root->_left);int rightH = _Height(root->_right);if (rightH - leftH != root->_bf){cout << root->_kv.first << "平衡因子異常" << endl;return false;}return abs(leftH - rightH) < 2&& _IsBalance(root->_left)&& _IsBalance(root->_right);}void RotateL(Node* parent)//左旋{Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;Node* ppnode = parent->_parent;subR->_left = parent;parent->_parent = subR;if (ppnode == nullptr){_root = subR;_root->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = subR;}else{ppnode->_right = subR;}subR->_parent = ppnode;}parent->_bf = subR->_bf = 0;}void RotateR(Node* parent)//右旋{Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* ppnode = parent->_parent;subL->_right = parent;parent->_parent = subL;if (parent == _root){_root = subL;_root->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = subL;}else{ppnode->_right = subL;}subL->_parent = ppnode;}subL->_bf = parent->_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 == 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);}}void RotateRL(Node* parent)//右左旋{Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(parent->_right);RotateL(parent);if (bf == 1){subR->_bf = 0;parent->_bf = -1;subRL->_bf = 0;}else if (bf == -1){subR->_bf = 1;parent->_bf = 0;subRL->_bf = 0;}else if (bf == 0){subR->_bf = 0;parent->_bf = 0;subRL->_bf = 0;}else{assert(false);}}void _InOrder(Node* root)//中序遍歷AVL樹{if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first << " ";_InOrder(root->_right);}
private:Node* _root = nullptr;
};int main()
{int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };AVLTree<int, int> t1;for (auto e : a){t1.Insert(make_pair(e, e));}t1.InOrder();cout << t1.IsBalance() << endl;return 0;
}

結語

? ? ? ? 以上就是關于AVL的講解,AVL的重點在于對旋轉的理解,理清旋轉的邏輯與各個節點之間的邏輯關系尤為重要。最后希望本文可以給你帶來更多的收獲,如果本文對你起到了幫助,希望可以動動小指頭幫忙點贊👍+關注😎+收藏👌!如果有遺漏或者有誤的地方歡迎大家在評論區補充,謝謝大家!!

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

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

相關文章

2024中國眼博會,山東省眼科醫學技術交流大會

以展帶會&#xff0c;以會促展&#xff0c;展與會有機結合&#xff0c;立足山東打造具全國影響力的眼康產業發展盛會&#xff1b; ——隨著時代的高速發展&#xff0c;科技的進步&#xff0c;現代生活節奏的加快&#xff0c;青少年近視問題日益嚴重&#xff0c;對兒童青少年的…

舊的Spring Security OAuth已停止維護,全面擁抱新解決方案Spring SAS

Spring Authorization Server 替換 Shiro 指引 背景 Spring 團隊正式宣布 Spring Security OAuth 停止維護&#xff0c;該項目將不會再進行任何的迭代 目前 Spring 生態中的 OAuth2 授權服務器是 Spring Authorization Server 已經可以正式生產使用作為 SpringBoot 3.0 的最新…

如何使用naive 做一個模態框的方式

1.我的問題使用了一個table 表格&#xff0c;在表格中設置倆個按鈕 最后做出來的效果 <template><div><h1>測試文件</h1><!-- 表格 --><n-data-table :columns"columns" :data"data" :pagination"pagination" …

Linux內核隊列queue.h

文章目錄 一、簡介二、SLIST單向無尾鏈表2.1 介紹2.2 操作2.3 例子 三、STAILQ單向有尾鏈表四、LIST雙向無尾鏈表五、TAILQ雙向有尾鏈表六、CIRCLEQ循環鏈表七、queue源碼參考 一、簡介 queue.h是一個非常經典的文件&#xff0c;定義了一系列宏的操作&#xff0c;它定義了一系…

筆記72:關于IMU(慣性測量單元)傳感器的作用【不涉及公式推導】

一、IMU傳感器是什么&#xff1a; 慣性測量單元IMU&#xff08;Inertial Measurement Unit&#xff09;是一種使用【加速度計】和【陀螺儀】來測量【物體三軸姿態角&#xff08;空間姿態&#xff09;】的裝置&#xff1b;IMU在坐標系的每個坐標軸上&#xff0c;均安裝有1個陀螺…

90-子集2(回溯算法)

題目 給你一個整數數組 nums &#xff0c;其中可能包含重復元素&#xff0c;請你返回該數組所有可能的子集&#xff08;冪集&#xff09;。 解集 不能 包含重復的子集。返回的解集中&#xff0c;子集可以按 任意順序 排列。 示例 1&#xff1a; 輸入&#xff1a;nums [1,2,2] …

深入理解CSS常見選擇器

標題&#xff1a;深入理解CSS常見選擇器 在CSS中&#xff0c;選擇器是一種強大的工具&#xff0c;用于定位和樣式化HTML文檔中的元素。通過選擇器的靈活運用&#xff0c;我們能夠精準地選擇需要操作的元素&#xff0c;從而實現豐富多彩的頁面布局和設計。本文將重點介紹常見的…

Vue2:用node+express部署Vue項目

一、編譯項目 命令 npm run build執行命令后&#xff0c;我們會在項目文件夾中看到如下生成的文件 二、部署Vue項目 接上一篇&#xff0c;nodeexpress編寫輕量級服務 1、在demo中創建static文件夾 2、將dist目錄中的文件放入static中 3、修改server.js文件 關鍵配置&…

全量知識系統問題及SmartChat給出的答復 之13 解析器+DDD+文法型

Q32. DDD的領域概念和知識系統中設計的解析器之間的關系。 那下面&#xff0c;我們回到前面的問題上來。 前面說到了三種語法解析器&#xff0c;分別是 形式語言的&#xff08;機器或計算機語言&#xff09;、人工語言的和自然語言的。再前面&#xff0c;我們聊到了DDD設計思…

基于java的學生派遣信息管理系統設計開題報告

歡迎添加微信互相交流學習哦&#xff01; 項目源碼&#xff1a;biye2: 畢業設計源碼 一、項目名稱 Java基于學生派遣信息管理系統設計 二、項目背景 隨著科技的發展&#xff0c;互聯網在我國的應用越來越廣泛&#xff0c;尤其是在教育領域。為了能更好地管理學生派遣信息&am…

DayDreamInGIS 之 ArcGIS Pro二次開發 圖層屬性中換行符等特殊字符替換

具體參考ArcMap中類似的問題&#xff0c;本帖開發一個ArcGISPro版的工具 1.基礎庫部分 插件開發&#xff0c;經常需要處理圖層與界面的交互。基礎庫把常用的交互部分做了封裝&#xff0c;方便之后的重復使用。 &#xff08;1&#xff09;下述類定義了數據存儲結構&#xff0…

DFA還原白盒AES密鑰

本期內容是關于某app模擬登錄的,涉及的知識點比較多,有unidbg補環境及輔助還原算法,ida中的md5以及白盒aes,fart脫殼,frida反調試 本章所有樣本及資料均上傳到了123云盤 llb資料官方版下載丨最新版下載丨綠色版下載丨APP下載-123云盤 目錄 首先抓包 fart脫殼 加密位置定位…

0048__Unix傳奇

Unix傳奇 &#xff08;上篇&#xff09;_unix傳奇(上篇)-CSDN博客 Unix傳奇 &#xff08;下篇&#xff09;-CSDN博客 Unix現狀與未來——CSDN對我的采訪_nuix郵件系統行業地位-CSDN博客

win11安裝nodejs

一、下載安裝包 鏈接: https://pan.baidu.com/s/1_df8s1UlgNNaewWrWgI59A?pwdpsjm 提取碼: psjm 二、安裝步驟 1.雙擊安裝包 2.Next> 3.勾選之后&#xff0c;Next> 4.點擊Change&#xff0c;選擇你要安裝的路徑&#xff0c;然后Next> 5.點擊Install安裝 二、…

學生云服務器騰訊云_騰訊云學生學生_騰訊云學生云主機

2024年騰訊云學生服務器優惠活動「云校園」&#xff0c;學生服務器優惠價格&#xff1a;輕量應用服務器2核2G學生價30元3個月、58元6個月、112元一年&#xff0c;輕量應用服務器4核8G配置191.1元3個月、352.8元6個月、646.8元一年&#xff0c;CVM云服務器2核4G配置842.4元一年&…

基于擴散模型的圖像編輯:首篇綜述

AIGC 大模型最火熱的任務之一——基于 Diffusion Model 的圖像編輯(editing)領域的首篇綜述。長達 26 頁&#xff0c;涵蓋 297 篇文獻&#xff01;本文全面研究圖像編輯前沿方法&#xff0c;并根據技術路線精煉地劃分為 3 個大類、14 個子類&#xff0c;通過表格列明每個方法的…

查詢緩存-緩存更新-緩存穿透-緩存雪崩-緩存擊穿

1.查詢緩存 1.2.出現的原因 用戶高并發訪問帶來的服務器讀寫的壓力 1.3.解決方法 添加緩存 2.緩存更新 2.1.出現的原因 出現數據不一致的問題 2.2.解決方法 操作數據庫的時候 更新數據庫刪除緩存 查詢數據的時候設置過期時間 3.緩存穿透 3.1.出現的原因 在高并發訪…

LeetCode 熱題 100 | 圖論(一)

目錄 1 200. 島嶼數量 2 994. 腐爛的橘子 2.1 智障遍歷法 2.2 仿層序遍歷法 菜鳥做題&#xff0c;語言是 C 1 200. 島嶼數量 解題思路&#xff1a; 遍歷二維數組&#xff0c;尋找 “1”&#xff08;若找到則島嶼數量 1&#xff09;尋找與當前 “1” 直接或間接連接在…

Java輸入輸出流詳細解析

Java I/O&#xff08;輸入/輸出&#xff09;主要被用來處理輸入數據和輸出結果。 在Java中&#xff0c;輸入/輸出操作被當作流&#xff08;Stream&#xff09;進行處理。流是一個連續的數據流入或數據流出的通道。流操作在Java中主要可以分為兩種類型&#xff1a;字節流和字符…

基于ssm疫情期間高校防控系統+vue論文

摘 要 傳統信息的管理大部分依賴于管理人員的手工登記與管理&#xff0c;然而&#xff0c;隨著近些年信息技術的迅猛發展&#xff0c;讓許多比較老套的信息管理模式進行了更新迭代&#xff0c;學生信息因為其管理內容繁雜&#xff0c;管理數量繁多導致手工進行處理不能滿足廣大…