天津校園文化設(shè)計(jì)公司湖南網(wǎng)站建設(shè)推廣優(yōu)化
?<1>主頁(yè):我的代碼愛(ài)吃辣
📃<2>知識(shí)講解:數(shù)據(jù)結(jié)構(gòu)——AVL樹(shù)
??<3>開(kāi)發(fā)環(huán)境:Visual Studio 2022
💬<4>前言:AVL樹(shù)是對(duì)二叉搜索樹(shù)的嚴(yán)格高度控制,所以AVL樹(shù)的搜索效率很高,但是這是需要付出很大的代價(jià)的,要維護(hù)父親指針,和平衡因子。
目錄
一.AVL的概念
二. AVL樹(shù)節(jié)點(diǎn)及整體結(jié)構(gòu)的定義
?三. AVL樹(shù)的插入
1. 先按照二叉搜索樹(shù)的規(guī)則將節(jié)點(diǎn)插入到AVL樹(shù)中
2.根據(jù)插入的位置調(diào)整平衡因子
四.AVL樹(shù)的旋轉(zhuǎn)
1.左單旋
2.右單旋
3.左右雙旋
4.右左雙旋
5.總結(jié):
五.AVL樹(shù)的刪除(了解)
六.AVL樹(shù)的性能
七.完整代碼及測(cè)試
一.AVL的概念
二叉搜索樹(shù)雖可以縮短查找的效率,但如果數(shù)據(jù)有序或接近有序二叉搜索樹(shù)將退化為單支樹(shù),查
找元素相當(dāng)于在順序表中搜索元素,效率低下。因此,兩位俄羅斯的數(shù)學(xué)家G.M.Adelson-Velskii
和E.M.Landis在1962年發(fā)明了一種解決上述問(wèn)題的方法:當(dāng)向二叉搜索樹(shù)中插入新結(jié)點(diǎn)后,如果能保證每個(gè)結(jié)點(diǎn)的左右子樹(shù)高度之差的絕對(duì)值不超過(guò)1(需要對(duì)樹(shù)中的結(jié)點(diǎn)進(jìn)行調(diào)整),即可降低樹(shù)的高度,從而減少平均搜索長(zhǎng)度。
一棵AVL樹(shù)或者是空樹(shù),或者是具有以下性質(zhì)的二叉搜索樹(shù):
- 它的左右子樹(shù)都是AVL樹(shù)
- 左右子樹(shù)高度之差(簡(jiǎn)稱平衡因子)的絕對(duì)值不超過(guò)1(-1/0/1)
?
如果一棵二叉搜索樹(shù)是高度平衡的,它就是AVL樹(shù)。如果它有n個(gè)結(jié)點(diǎn),其高度可保持在
O(),搜索時(shí)間復(fù)雜度O(
)
二. AVL樹(shù)節(jié)點(diǎn)及整體結(jié)構(gòu)的定義
//AVL樹(shù)結(jié)點(diǎn)
template<class K, class V>
struct AVLTreeNode
{AVLTreeNode(pair<K,V> kv):_kv(kv),_bf(0),_left(nullptr),_right(nullptr),_parent(nullptr){}pair<K, V> _kv; //Key/Value數(shù)據(jù)int _bf; //平衡因子AVLTreeNode<K, V>* _left; //結(jié)點(diǎn)的左子樹(shù)AVLTreeNode<K, V>* _right; //結(jié)點(diǎn)的右子樹(shù)AVLTreeNode<K, V>* _parent; //結(jié)點(diǎn)的雙親
};//AVL樹(shù)定義
template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public:bool insert(pair<K,V> kv){}
private:Node* _root = nullptr;
};
?三. AVL樹(shù)的插入
AVL樹(shù)就是在二叉搜索樹(shù)的基礎(chǔ)上引入了平衡因子,因此AVL樹(shù)也可以看成是二叉搜索樹(shù)。那么
AVL樹(shù)的插入過(guò)程可以分為兩步:
- 按照二叉搜索樹(shù)的方式插入新節(jié)點(diǎn)
- 調(diào)整節(jié)點(diǎn)的平衡因子
插入過(guò)程:
1. 先按照二叉搜索樹(shù)的規(guī)則將節(jié)點(diǎn)插入到AVL樹(shù)中
bool insert(pair<K,V> kv){if (_root == nullptr){_root = new Node(kv);return true;}Node* cur = _root;//記錄當(dāng)前結(jié)點(diǎn)Node* parent = nullptr;//記錄父親結(jié)點(diǎn)while (cur){if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else{return false;}}//找到了合適的位置,創(chuàng)建新節(jié)點(diǎn),出入位置cur = new Node(kv);//修改新節(jié)點(diǎn)的指向if (kv.first < parent->_kv.first){parent->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;//未完待續(xù)....}
2.根據(jù)插入的位置調(diào)整平衡因子
平衡因子:右子樹(shù)高度減去左子樹(shù)高度。
cur 插入后,parent 的平衡因子一定需要調(diào)整,在插入之前,pParent 的平衡因子分為三種情況:-1,0, 1, 分以下兩種情況:
- ?如果cur插入到parent的左側(cè),只需給parent的平衡因子-1即可
- 如果cur插入到parent的右側(cè),只需給parent的平衡因子+1即可
此時(shí):parent的平衡因子可能有三種情況:0,正負(fù)1, 正負(fù)2
- 如果parent的平衡因子為0,說(shuō)明插入之前parent的平衡因子為正負(fù)1,插入后被調(diào)整成0,此時(shí)滿足? AVL樹(shù)的性質(zhì),插入成功。
- 如果parent的平衡因子為正負(fù)1,說(shuō)明插入前parent的平衡因子一定為0,插入后被更新成正負(fù)1,此 時(shí)以parent為根的樹(shù)的高度增加,需要繼續(xù)向上更新。
- 如果parent的平衡因子為正負(fù)2,則parent的平衡因子違反平衡樹(shù)的性質(zhì),需要對(duì)其進(jìn)行旋轉(zhuǎn)處理。
while (parent){//cur插入到parent的左側(cè)if (parent->_left == cur){parent->_bf--;}else//cur插入到parent的右側(cè){parent->_bf++;}//需向上調(diào)整平衡因子if (parent->_bf == 1||parent->_bf==-1){cur = parent;parent = cur->_parent;}//無(wú)需向上調(diào)整平衡因子else if(parent->_bf==0){break;}//無(wú)需向上調(diào)整平衡因子,直接旋轉(zhuǎn)處理else if (parent->_bf == 2||parent->_bf==-2){//旋轉(zhuǎn),旋轉(zhuǎn)之后平衡因子已經(jīng)平衡,可以直接推出break;}else//出現(xiàn)的其他的錯(cuò)誤情況{assert(0);}}
四.AVL樹(shù)的旋轉(zhuǎn)
如果在一棵原本是平衡的AVL樹(shù)中插入一個(gè)新節(jié)點(diǎn),可能造成不平衡,此時(shí)必須調(diào)整樹(shù)的結(jié)構(gòu),
使之平衡化。根據(jù)節(jié)點(diǎn)插入位置的不同,AVL樹(shù)的旋轉(zhuǎn)分為四種:
1.左單旋
新節(jié)點(diǎn)插入較高右子樹(shù)的右側(cè)
上圖在插入前,AVL樹(shù)是平衡的,新節(jié)點(diǎn)插入到60的右子樹(shù)中,30右子樹(shù)增加了一層,導(dǎo)致以60為根的二叉樹(shù)不平衡,要讓30平衡,只能將30右子樹(shù)的高度減少一層,左子樹(shù)增加一層,即將右子樹(shù)往上提,這樣30轉(zhuǎn)下來(lái),因?yàn)?0比60小,只能將其放在60的左子樹(shù),而如果60有左子樹(shù),左子樹(shù)根的值一定大于30,小于60,只能將其放在30的右子樹(shù),旋轉(zhuǎn)完成后,更新節(jié)點(diǎn)的平衡因子即可。在旋轉(zhuǎn)過(guò)程中,有以下幾種情況需要考慮:
- 60節(jié)點(diǎn)的左孩子可能存在,也可能不存在。
- 30可能是整棵樹(shù)根節(jié)點(diǎn),也可能是子樹(shù)根節(jié)點(diǎn)。
? 如果是整棵樹(shù)根節(jié)點(diǎn),旋轉(zhuǎn)完成后,要更整棵樹(shù)新根節(jié)點(diǎn);如果是子樹(shù)根節(jié)點(diǎn),可能是某個(gè)節(jié)點(diǎn)的左子樹(shù),也可能是右子樹(shù)。
void RotateL(Node* parent){// a// b// c//找到需要旋轉(zhuǎn)的結(jié)點(diǎn)Node* curR = parent->_right;Node* curRL = curR->_left;//調(diào)整結(jié)點(diǎn),并且修改其父親結(jié)點(diǎn)指針parent->_right = curRL;if (curRL)//可能為空{(diào)curRL->_parent = parent;}//在修改子樹(shù)根節(jié)點(diǎn)之前,保存子樹(shù)根節(jié)點(diǎn)的父親Node* pparent = parent->_parent;//修改子樹(shù)根節(jié)點(diǎn)curR->_left = parent;parent->_parent = curR;//子樹(shù)根節(jié)點(diǎn)有可能是整棵樹(shù)的根節(jié)點(diǎn)if (pparent == nullptr){_root = curR;_root->_parent = nullptr;}else//子樹(shù)根節(jié)點(diǎn)不是整棵樹(shù)的根節(jié)點(diǎn){//還要看子樹(shù)是它父親的左孩子還是右孩子if (pparent->_left == parent){pparent->_left = curR;}else{pparent->_right = curR;}}//修改平衡因子curR->_bf = parent->_bf = 0;}
2.右單旋
新節(jié)點(diǎn)插入較高左子樹(shù)的左側(cè)
?右單旋過(guò)程和左單旋轉(zhuǎn)過(guò)程一模一樣僅僅只是反過(guò)來(lái)。
void RotateR(Node* parent){Node* curL = parent->_left;Node* curLR = curL->_right;parent->_left = curLR;if (curLR){curLR->_parent = parent;}Node* pparent = parent->_parent;curL->_right = parent;parent->_parent = curL;if (pparent == nullptr){_root = curL;_root->_parent = nullptr;}else{if (pparent->_left == parent){pparent->_left = curL;}else{pparent->_right = curL;}}curL->_bf = parent->_bf = 0;}
3.左右雙旋
新節(jié)點(diǎn)插入較高左子樹(shù)的右側(cè)---左右:先左單旋再右單旋
?將雙旋變成單旋后再旋轉(zhuǎn),即:先對(duì)30進(jìn)行左單旋,然后再對(duì)90進(jìn)行右單旋,旋轉(zhuǎn)完成后再
考慮平衡因子的更新。
注意:旋轉(zhuǎn)之前,60的平衡因子可能是 -1 / 0 / 1,旋轉(zhuǎn)完成之后,根據(jù)情況對(duì)其他節(jié)點(diǎn)的平衡因子進(jìn)行調(diào)整。
?當(dāng)h=0,時(shí)60自己就是一個(gè)新插入的結(jié)點(diǎn),此時(shí)他的平衡因子就是。
所以旋轉(zhuǎn)之前,需要保存curLR的平衡因子,旋轉(zhuǎn)完成之后,需要根據(jù)該平衡因子來(lái)調(diào)整其他節(jié)
點(diǎn)的平衡因子。
4.右左雙旋
右左雙旋和左右雙旋過(guò)程一模一樣,僅僅只是反過(guò)來(lái)。
void RotateRL(Node* parent){Node* curR = parent->_left;Node* curRL = curR->_right;int curRL_bf = curRL->_bf;RotateL(curR);RotateR(parent);if (curRL_bf == -1){parent->_bf = 0;curRL->_bf = 0;curR->_bf = 1;}else if (curRL_bf == 1){parent->_bf = -1;curRL->_bf = 0;curR->_bf = 1;}else if (curRL_bf == 0){parent->_bf = 0;curRL->_bf = 0;curR->_bf = 0;}else{assert(false);}}
5.總結(jié):
假如以parent為根的子樹(shù)不平衡,即parent的平衡因子為2或者-2,分以下情況考慮
1. parent的平衡因子為2,說(shuō)明parent的右子樹(shù)高,設(shè)parent的右子樹(shù)的根為curR
- 當(dāng)curR的平衡因子為1時(shí),執(zhí)行左單旋
- 當(dāng)curR的平衡因子為-1時(shí),執(zhí)行右左雙旋
2. parent的平衡因子為-2,說(shuō)明parent的左子樹(shù)高,設(shè)parent的左子樹(shù)的根為curL
- 當(dāng)curL的平衡因子為-1是,執(zhí)行右單旋
- 當(dāng)curL的平衡因子為1時(shí),執(zhí)行左右雙旋
旋轉(zhuǎn)完成后,原parent為根的子樹(shù)個(gè)高度降低,已經(jīng)平衡,不需要再向上更新。
//調(diào)整平衡因子while (parent){if (parent->_left == cur){parent->_bf--;}else{parent->_bf++;}if (parent->_bf == 1||parent->_bf==-1)//需向上調(diào)整平衡因子{cur = parent;parent = cur->_parent;}else if(parent->_bf==0)//無(wú)需向上調(diào)整平衡因子{break;}else if (parent->_bf == 2||parent->_bf==-2)//無(wú)需向上調(diào)整平衡因子,直接旋轉(zhuǎn){if (parent->_bf == 2 && parent->_right->_bf == 1){RotateL(parent);//左單旋}else if (parent->_bf == -2 && parent->_left->_bf == -1){RotateR(parent);//右單旋}else if (parent->_bf == 2 && parent->_left->_bf == -1){RotateRL(parent);//右左雙旋}else if (parent->_bf == -2 && parent->_left ->_bf== 1){RotateLR(parent);//左右雙旋}else{assert(false);//其他錯(cuò)誤情況}break;}else{assert(0);}
五.AVL樹(shù)的刪除(了解)
因?yàn)锳VL樹(shù)也是二叉搜索樹(shù),可按照二叉搜索樹(shù)的方式將節(jié)點(diǎn)刪除,然后再更新平衡因子,只不
錯(cuò)與刪除不同的時(shí),刪除節(jié)點(diǎn)后的平衡因子更新,最差情況下一直要調(diào)整到根節(jié)點(diǎn)的位置。
具體實(shí)現(xiàn)學(xué)生們可參考《算法導(dǎo)論》或《數(shù)據(jù)結(jié)構(gòu)-用面向?qū)ο蠓椒ㄅcC++描述》殷人昆版。
六.AVL樹(shù)的性能
AVL樹(shù)是一棵絕對(duì)平衡的二叉搜索樹(shù),其要求每個(gè)節(jié)點(diǎn)的左右子樹(shù)高度差的絕對(duì)值都不超過(guò)1,這
樣可以保證查詢時(shí)高效的時(shí)間復(fù)雜度,即。但是如果要對(duì)AVL樹(shù)做一些結(jié)構(gòu)修改的操作,性能非常低下,比如:插入時(shí)要維護(hù)其絕對(duì)平衡,旋轉(zhuǎn)的次數(shù)比較多,更差的是在刪除時(shí),有可能一直要讓旋轉(zhuǎn)持續(xù)到根的位置。因此:如果需要一種查詢高效且有序的數(shù)據(jù)結(jié)構(gòu),而且數(shù)據(jù)的個(gè)數(shù)為靜態(tài)的(即不會(huì)改變),可以考慮AVL樹(shù),但一個(gè)結(jié)構(gòu)經(jīng)常修改,就不太適合。
七.完整代碼及測(cè)試
AVL.hpp
#pragma once
#include<iostream>
#include<cassert>
using namespace std;template<class K, class V>
struct AVLTreeNode
{AVLTreeNode(pair<K,V> kv):_kv(kv),_bf(0),_left(nullptr),_right(nullptr),_parent(nullptr){}pair<K, V> _kv; //Key/Value數(shù)據(jù)int _bf; //平衡因子AVLTreeNode<K, V>* _left; //結(jié)點(diǎn)的左子樹(shù)AVLTreeNode<K, V>* _right; //結(jié)點(diǎn)的右子樹(shù)AVLTreeNode<K, V>* _parent; //結(jié)點(diǎn)的雙親
};template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public:bool insert(pair<K,V> kv){if (_root == nullptr){_root = new Node(kv);return true;}Node* cur = _root;Node* parent = nullptr;while (cur){if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else{return false;}}//找到了合適的位置cur = new Node(kv);//if (kv.first < parent->_kv.first){parent->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;//調(diào)整平衡因子while (parent){if (parent->_left == cur){parent->_bf--;}else{parent->_bf++;}if (parent->_bf == 1||parent->_bf==-1)//需向上調(diào)整平衡因子{cur = parent;parent = cur->_parent;}else if(parent->_bf==0)//無(wú)需向上調(diào)整平衡因子{break;}else if (parent->_bf == 2||parent->_bf==-2)//無(wú)需向上調(diào)整平衡因子,直接旋轉(zhuǎn){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){RotateRL(parent);//右左雙旋}else if (parent->_bf == -2 && cur ->_bf== 1){RotateLR(parent);//左右雙旋}else{//cout << parent->_bf << ":" << /*parent->_left->_bf << ":" <<*/ parent->_right->_bf << endl;assert(false);//其他錯(cuò)誤情況}break;}else{assert(false);}}return true;}void Inorder(){_inorder(_root);cout << endl;}private:void _inorder(Node* root){if (root == nullptr){return;}_inorder(root->_left);cout << root->_kv.first << " ";_inorder(root->_right);}void RotateL(Node* parent){// a// b// c//找到需要旋轉(zhuǎn)的結(jié)點(diǎn)Node* curR = parent->_right;Node* curRL = curR->_left;//調(diào)整結(jié)點(diǎn),并且修改其父親結(jié)點(diǎn)指針parent->_right = curRL;if (curRL)//可能為空{(diào)curRL->_parent = parent;}//在修改子樹(shù)根節(jié)點(diǎn)之前,保存子樹(shù)根節(jié)點(diǎn)的父親Node* pparent = parent->_parent;//修改子樹(shù)根節(jié)點(diǎn)curR->_left = parent;parent->_parent = curR;//子樹(shù)根節(jié)點(diǎn)有可能是整棵樹(shù)的根節(jié)點(diǎn)if (pparent == nullptr){_root = curR;_root->_parent = nullptr;}else//子樹(shù)根節(jié)點(diǎn)不是整棵樹(shù)的根節(jié)點(diǎn){//還要看子樹(shù)是它父親的左孩子還是右孩子if (pparent->_left == parent){pparent->_left = curR;}else{pparent->_right = curR;}curR->_parent = pparent;}//修改平衡因子curR->_bf = parent->_bf = 0;}void RotateR(Node* parent){Node* curL = parent->_left;Node* curLR = curL->_right;parent->_left = curLR;if (curLR){curLR->_parent = parent;}Node* pparent = parent->_parent;curL->_right = parent;parent->_parent = curL;if (parent == _root){_root = curL;_root->_parent = nullptr;}else{if (pparent->_left == parent){pparent->_left = curL;}else{pparent->_right = curL;}curL->_parent = pparent;}curL->_bf = parent->_bf = 0;}void RotateLR(Node* parent){Node* curL = parent->_left;Node* curLR = curL->_right;//旋轉(zhuǎn)之前,保存pSubLR的平衡因子,旋轉(zhuǎn)完成之后,//需要根據(jù)該平衡因子來(lái)調(diào)整其他節(jié)點(diǎn)的平衡因子int curLR_bf = curLR->_bf;//RotateL(curL);RotateR(parent);if (curLR_bf == -1){parent->_bf = 1;curLR->_bf = 0;curL->_bf = 0;}else if (curLR_bf == 1){parent->_bf = 0;curL->_bf = -1;curLR->_bf = 0;}else if (curLR_bf == 0){parent->_bf = 0;curL->_bf = 0;curLR->_bf = 0;}else{assert(false);}}void RotateRL(Node* parent){Node* curR = parent->_right;Node* curRL = curR->_left;int curRL_bf = curRL->_bf;RotateR(curR);RotateL(parent);if (curRL_bf == -1){parent->_bf = 0;curRL->_bf = 0;curR->_bf = 1;}else if (curRL_bf == 1){parent->_bf = -1;curRL->_bf = 0;curR->_bf = 0;}else if (curRL_bf == 0){parent->_bf = 0;curRL->_bf = 0;curR->_bf = 0;}else{assert(false);}}Node* _root = nullptr;
};
test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include"AVL.hpp"
int main()
{int arr1[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };int arr2[] = {4, 2, 6, 1, 3, 5, 15, 7, 16, 14};AVLTree<int, int> a1;AVLTree<int, int> a2;for (auto e : arr1){a1.insert(make_pair(e,e));}a1.Inorder();for (auto e : arr2){a2.insert(make_pair(e, e));}a2.Inorder();
}
?