数据结构 - AVL树

文章目录

    • 一、AVL树的介绍
    • 二、AVL树的实现
      • 1、基本框架
      • 2、查找
      • 3、插入
      • 4、删除
      • 5、测试
      • 6、总代码
    • 三、AVL树的性能


一、AVL树的介绍

1、概念

AVL树(Adelson-Velsky and Landis Tree)是一种自平衡的二叉搜索树。它得名于其发明者G. M. Adelson-Velsky和E. M. Landis。在AVL树中,任何节点的两个子树的高度最大差别为1,这保证了树的平衡性,从而避免了在极端情况下(如数据有序或接近有序时)二叉搜索树退化为链表,导致操作效率低下的问题。

在这里插入图片描述

2、特点

(1)平衡性:AVL树通过维护每个节点的平衡因子(左子树高度与右子树高度之差)来保持树的平衡。平衡因子的值只能是-1、0或1。如果某个节点的平衡因子绝对值大于1,那么该树就失去了平衡,需要通过旋转操作来重新平衡。

(2)旋转操作:当AVL树失去平衡时,会触发旋转操作来恢复平衡。旋转操作主要有四种:右旋(单旋)、左旋(单旋)、右-左双旋和左-右双旋。这些旋转操作通过改变树中节点的链接关系来降低树的高度,从而保持树的平衡。

(3) 高效的查找、插入和删除操作:由于AVL树保持了平衡,其查找、插入和删除操作的时间复杂度都能保持在O(log
n)的范围内,其中n是树中节点的数量。这使得AVL树在处理大量数据时能够保持高效的性能。

(4)空间开销:与普通的二叉搜索树相比,AVL树需要额外的空间来存储每个节点的平衡因子。这增加了树的空间开销,但在大多数情况下,这种开销是可以接受的。

3、应用场景

AVL树广泛应用于需要频繁插入、删除和查找操作的场景,如数据库索引、文件系统的目录结构、实时数据更新等。在这些场景中,AVL树能够保持高效的性能,确保数据处理的快速响应。

4、缺点

尽管AVL树具有许多优点,但它也有一些缺点。例如,在每次插入或删除节点后都需要进行平衡检查和可能的旋转操作,这增加了操作的复杂度。此外,与红黑树等其他自平衡二叉搜索树相比,AVL树在插入和删除操作时可能需要更多的旋转操作来保持平衡。因此,在某些特定场景下,红黑树可能比AVL树更受欢迎。然而,在需要高度平衡的场合,AVL树仍然是一个非常好的选择。

二、AVL树的实现

1、基本框架

树节点:


//树节点
template<class K, class V>
struct AVLTreeNode
{//构造函数AVLTreeNode(const pair<K, V>& val = pair<K, V>()): _left(nullptr), _right(nullptr), _parent(nullptr), _val(val), _bf(0){}AVLTreeNode<K,V>* _left;	//左孩子指针AVLTreeNode<K, V>* _right;	//右孩子指针AVLTreeNode<K, V>* _parent;	//父亲指针pair<K, V> _val;	//数据int _bf;   //节点的平衡因子 -> 右子树高度 - 左子树高度
};

AVL树类:

template<class K, class V>
class AVLTree
{//树节点typedef AVLTreeNode<K, V> Node;private://根节点Node* _root = nullptr;
};

2、查找

AVL树的查找操作与普通的二叉搜索树(BST)的查找操作非常相似。由于AVL树本身就是一种二叉搜索树,所以它可以利用二叉搜索树的性质来高效地查找元素。在AVL树中查找元素的过程基本上不涉及树的平衡操作(如旋转),只是简单地利用节点的值和树的结构来定位目标元素。

步骤:从根节点(cur)开始,通过判断key与当前节点key的大小来决定是去左子树还是右子树找该值,循环迭代(cur)上述过程直到找到相同的key后返回该节点,否则就返回空。

//查找
Node* Find(const K& key)
{//从根节点开始Node* cur = _root;while (cur){//大了就去左子树中搜索if (cur->_val.first > key){cur = cur->_left;}//小了就去右子树中搜索else if (cur->_val.first < key){cur = cur->_right;}else{//找到返回当前节点return cur;}}return nullptr;
}

在这里插入图片描述

3、插入

AVL树的插入操作在二叉搜索树(BST)的插入基础上增加了维护树平衡的步骤。AVL树是一种自平衡的二叉搜索树,其中任何节点的两个子树的高度最大差别为1。

在插入后需要更新平衡因子,插入左子树节点时,父节点的平衡因子-1,插入右子树节点时,父节点的平衡因子+1,当更新后的父节点的平衡因子为0就不需要向上更新了(说明插入之前是-1或者1,插入节点后不会影响到上面的节点的平衡因子),当平衡因子(右子树高度 - 左子树高度)为1或者-1时需要向上更新平衡因子(说明插入之前为0,插入后会影响到上面节点的平衡因子),当为2或者-2时说明该树失去平衡了,需要旋转来调整高度,旋转之后的高度就平衡了不需要向上更新。

在这里插入图片描述

(1)插入一个元素使左子树高度大于右子树且失去平衡(单纯一边高(L和parent平衡因子同号)) ------- 右旋转
在这里插入图片描述
平衡因子:根据图分析,旋转后parent和L都为0

// 右单旋
void RotateR(Node* parent)
{//左节点Node* L = parent->_left;//左子树右边第一个节点Node* Lr = L->_right;//parent的父亲Node* pparent = parent->_parent;//连接过程L->_right = parent;parent->_parent = L;//该节点可能为空if (Lr){Lr->_parent = parent;}parent->_left = Lr;//更新L的父节点L->_parent = pparent;//是根的情况if (pparent == nullptr){_root = L;}else{if (parent == pparent->_left) pparent->_left = L;else pparent->_right = L;}//更新后平衡因子都为0parent->_bf = L->_bf = 0;
}

(2)插入一个元素使右子树高度大于左子树且失去平衡(单纯一边高(R和parent平衡因子同号)) ------- 左旋转
在这里插入图片描述

平衡因子:根据图分析,旋转后parent和R都为0

//左旋转
void RotateL(Node* parent)
{//右边第一个节点Node* R = parent->_right;//右子树第一个左节点Node* Rl = R->_left;//父节点Node* pparent = parent->_parent;//连接过程parent->_right = Rl;if (Rl){Rl->_parent = parent;}R->_left = parent;//更新parent的父节点parent->_parent = R;//更新R的父节点R->_parent = pparent;//是根的情况if (nullptr == pparent){_root = R;}else{if (pparent->_left == parent) pparent->_left = R;else pparent->_right = R;}//更新平衡因子parent->_bf = R->_bf = 0;
}

(3)插入一个元素使左子树高度大于右子树且失去平衡(不单纯一边高(L和parent平衡因子异号)) ------- 左右旋转
在这里插入图片描述

平衡因子:根据图分析(上面只有Lr = 1 的情况,Lr = 0,或者Lr = -1的情况也按上图的方式推导),更新前Lr的平衡因子为1时更新后L的平衡因子为-1、parent和Lr为0,更新前Lr的平衡因子为-1时更新后parent平衡因子为1、L和Lr平衡因子为0,更新前Lr的平衡因子为0时,L、Lr、parent平衡因子都为0。

// 左右双旋
void RotateLR(Node* parent)
{Node* L = parent->_left;Node* Lr = L->_right;//先保存Lr的平衡因子,因为旋转之后Lr的平衡因子会变int bf = Lr->_bf;//左右双旋RotateL(L);RotateR(parent);//更新平衡因子if (bf == -1){parent->_bf = 1;L->_bf = 0;Lr->_bf = 0;}else if (bf == 1){parent->_bf = 0;L->_bf = -1;Lr->_bf = 0;}else if (bf == 0){parent->_bf = 0;L->_bf = 0;Lr->_bf = 0;}elseassert(false);
}

(4)插入一个元素使右子树高度大于左子树且失去平衡(不单纯一边高(R和parent平衡因子异号)) ------- 右左旋转
在这里插入图片描述
平衡因子:根据图分析(上面只有Rl = 1 的情况,Rl = 0,或者Rl = -1的情况也按上图的方式推导),更新前Rl的平衡因子为-1时更新后R的平衡因子为-1、parent和Rl为0,更新前Rl的平衡因子为1时更新后parent平衡因子为-1、R和Rl平衡因子为0,更新前Rl的平衡因子为0时,R、Rl、parent平衡因子都为0。

// 右左双旋
void RotateRL(Node* parent)
{Node* R = parent->_right;Node* Rl = R->_left;//先保存平衡因子int bf = Rl->_bf;//右左双旋RotateR(R);RotateL(parent);//更新平衡因子if (bf == 1){parent->_bf = -1;R->_bf = 0;Rl->_bf = 0;}else if (bf == -1){parent->_bf = 0;R->_bf = 1;Rl->_bf = 0;}else if (bf == 0){parent->_bf = 0;R->_bf = 0;Rl->_bf = 0;}elseassert(false);
}

(5)根据插入节点(参考搜索二叉树)+更新平衡因子完成插入

//插入
bool Insert(const pair<K, V>& val)
{	//找到放val的位置Node* cur = _root;//作为前驱指针,与val节点连接Node* precursor = nullptr;while (cur){//向左if (cur->_val.first > val.first){precursor = cur;cur = cur->_left;}//向右else if (cur->_val.first < val.first){precursor = cur;cur = cur->_right;}//存在相同的值else{return false;}}//插入新节点cur = new Node(val);//不存在根节点,作为根节点if (precursor == nullptr) _root = cur;//连接在前驱指针左侧else if (precursor->_val.first > val.first){cur->_parent = precursor;precursor->_left = cur;}//连接在前驱指针右侧else{cur->_parent = precursor;precursor->_right = cur;}//更新平衡因子while (precursor){if (precursor->_left == cur)precursor->_bf--;elseprecursor->_bf++;//为0说明平衡了,不需要再更新了if (precursor->_bf == 0)break;//出现异常需要更新平衡因子,更新完就可以else if (precursor->_bf == 2 || precursor->_bf == -2){if (precursor->_bf == 2 && cur->_bf == 1){RotateL(precursor);}else if (precursor->_bf == -2 && cur->_bf == -1){RotateR(precursor);}else if (precursor->_bf == 2 && cur->_bf == -1){RotateRL(precursor);}else if (precursor->_bf == -2 && cur->_bf == 1){RotateLR(precursor);}else{assert(false);}//更新完平衡了不需要向上更新break;}//进行迭代(向上更新)cur = precursor;precursor = precursor->_parent;}return true;
}

4、删除

AVL树删除操作是一个复杂但关键的过程,因为它需要在删除节点后重新调整树的结构以保持其平衡性。AVL树是一种自平衡的二叉搜索树,其中任何节点的两个子树的高度最大差别为1。

在删除后需要更新平衡因子,删除左子树节点时,父节点的平衡因子+1,删除右子树节点时,父节点的平衡因子-1,当更新后的父节点的平衡因子为-1 或者 1就不需要向上更新了(说明删除之前0,删除节点后不会影响到上面的节点的平衡因子),当平衡因子0时需要向上更新平衡因子(说明删除之前为1或者-1,删除后会影响到上面节点的平衡因子),当为2或者-2时说明该树失去平衡了,需要旋转来调整高度,旋转之后当前父节点还是0的话还要继续向上更新,直到更新为-1或者1时就结束更新。

因为复用插入的旋转操作,所以在删除元素后有一些平衡因子在旋转过程中没有正确更新,此时我们就要在旋转完后再次更新。
(1)删除后,右边高了,当R = 0,或者 R = 1 时进行 — 左单旋
在这里插入图片描述
平衡因子:当R的平衡因子为0时,parent的平衡因子需要修改为1,R的平衡因子修改为-1,当R的平衡因子为1时(也是按上图方式推导),parent、R平衡因子都为0。

(2)删除后,左边高了,当L = 0,或者 L = -1 时进行 — 右单旋
在这里插入图片描述
平衡因子:当L的平衡因子为0时,parent的平衡因子需要修改为-1,L的平衡因子修改为-1,当L的平衡因子为-1时(也是按上图方式推导),parent、L平衡因子都为0。

(3)删除后,右边高了,并且出现异号 — 右左双旋

在这里插入图片描述

平衡因子:与插入时使用的右左双旋一样。

(4)删除后,左边高了,并且出现异号 — 左右双旋

在这里插入图片描述
平衡因子:与插入时使用的左右双旋一样。

(5)使用删除操作(参考搜索二叉树的删除)+旋转完成删除

//删除
bool Erase(const K& key)
{//从根节点开始搜索Node* cur = _root;//作为cur的前驱指针Node* precursor = nullptr;//搜索查找while (cur){if (cur->_val.first > key){precursor = cur;cur = cur->_left;}else if (cur->_val.first < key){precursor = cur;cur = cur->_right;}elsebreak;}//找不到if (cur == nullptr) return false;//假设cur左右节点都存在,找右边最小值替换if (cur->_left != nullptr && cur->_right != nullptr){Node* tmp1 = cur->_right;Node* tmp2 = nullptr;while (tmp1->_left){tmp2 = tmp1;tmp1 = tmp1->_left;}cur->_val = tmp1->_val;//tmp1左边没有节点,自己就是最小的节点if (tmp2 == nullptr){precursor = cur;cur = tmp1;	}else{cur = tmp1;precursor = tmp2;}}//假设左边为空和左右节点都为空int sign = 0;//左边为-1,右边为1Node* deletecur = cur;if (cur->_left == nullptr){左边为空,父节点为空,cur为根节点,让cur->_riggt做为根,直接结束就行了(cur->_riggt本身为平衡树)if (precursor == nullptr){_root = cur->_right;delete deletecur;return true;}else{if (precursor->_left == cur){precursor->_left = cur->_right;if (cur->_right == nullptr) sign = -1;}else{precursor->_right = cur->_right;if (cur->_right == nullptr) sign = 1;}}cur = cur->_right;}//假设右边为空else{//右边为空,父节点为空,cur为根节点,让cur->_left做为根,直接结束就行了(cur->_left本身为平衡树)if (precursor == nullptr){_root = cur->_left;delete deletecur;return true;}else{if (precursor->_left == cur)precursor->_left = cur->_left;elseprecursor->_right = cur->_left;}cur = cur->_left;}//更新平衡因子while (precursor){//cur出现空的情况if (cur == nullptr){if (sign == -1)precursor->_bf++;elseprecursor->_bf--;}else if (precursor->_left == cur)precursor->_bf++;elseprecursor->_bf--;if (precursor->_bf == -1 || precursor->_bf == 1)break;else if (precursor->_bf == -2 || precursor->_bf == 2){//右边高了左单旋if (precursor->_bf == 2 && (precursor->_right->_bf == 0 || precursor->_right->_bf == 1)){//R会做为新的precursor先保存Node* R = precursor->_right;int bf = precursor->_right->_bf;RotateL(precursor);//在旋转后的平衡因子不符合预期,需要更新if (bf == 0){precursor->_bf = 1;R->_bf = -1;}//更新precursor = R;}//左边高了右单旋else if (precursor->_bf == -2 && (precursor->_left->_bf == 0 || precursor->_left->_bf == -1)){//L会做为新的precursor先保存Node* L = precursor->_left;int bf = L->_bf;RotateR(precursor);//在旋转后的平衡因子不符合预期,需要更新if (bf == 0){precursor->_bf = -1;L->_bf = 1;}//更新precursor = L;}else if (precursor->_bf == 2 && precursor->_right->_bf == -1){//L会做为新的precursor先保存Node* R = precursor->_right;Node* L = R->_left;RotateRL(precursor);//更新precursor = L;}else if (precursor->_bf == -2 && precursor->_left->_bf== 1){//R会做为新的precursor先保存Node* L = precursor->_left;Node* R = L->_right;RotateLR(precursor);//更新precursor = R;}elseassert(false);//旋转完precursor更新了再次判断是否平衡了if (precursor->_bf == -1 || precursor->_bf == 1) break;}//进行迭代cur = precursor;precursor = precursor->_parent;}delete deletecur;return true;
}

5、测试

通过两颗子树的高度差是否大于1和判断高度差是否与这颗根节点平衡因子相等来判断是否为AVL树。

//判断是否为平衡树
bool IsBalanceTree()
{return _IsBalanceTree(_root);
}
//验证是否是平衡树
bool _IsBalanceTree(Node* root)
{if (root == nullptr) return true;int l = _Height(root->_left);int r = _Height(root->_right);int buff = r - l;if (root->_bf != buff || buff > 1 || buff < -1)return false;return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
}
//求树的高度
int _Height(Node* root)
{if (root == nullptr) return 0;int l = _Height(root->_left);int r = _Height(root->_right);return max(l, r) + 1;
}void test()
{AVLTree<int, int> a;vector<int> arr;for (int i = 0; i < 10000; i++){int m = rand() + i;arr.push_back(m);}//插入10000个随机数for (auto e :arr){a.Insert({ e,e });if (a.IsBalanceTree())cout << "是AVL树" << endl;elseassert(false);}//再全部删除for (auto e : arr){a.Erase(e);if (a.IsBalanceTree())cout << "是AVL树" << endl;elseassert(false);}
}

在这里插入图片描述
在插入和删除过程中没有报错,说明该树是AVL树。

6、总代码

#pragma once
#include<iostream>
#include<string>
#include<cassert>
#include<queue>
#include<vector>using namespace std;//树节点
template<class K, class V>
struct AVLTreeNode
{//构造函数AVLTreeNode(const pair<K, V>& val = pair<K, V>()): _left(nullptr), _right(nullptr), _parent(nullptr), _val(val), _bf(0){}AVLTreeNode<K,V>* _left;	//左孩子指针AVLTreeNode<K, V>* _right;	//右孩子指针AVLTreeNode<K, V>* _parent;	//父亲指针pair<K, V> _val;	//数据int _bf;   //节点的平衡因子 -> 右子树高度 - 左子树高度
};template<class K, class V>
class AVLTree
{//树节点typedef AVLTreeNode<K, V> Node;
public:~AVLTree(){DeleteTree(_root);}//插入bool Insert(const pair<K, V>& val){	//找到放val的位置Node* cur = _root;//作为前驱指针,与val节点连接Node* precursor = nullptr;while (cur){//向左if (cur->_val.first > val.first){precursor = cur;cur = cur->_left;}//向右else if (cur->_val.first < val.first){precursor = cur;cur = cur->_right;}//存在相同的值else{return false;}}//插入新节点cur = new Node(val);//不存在根节点,作为根节点if (precursor == nullptr) _root = cur;//连接在前驱指针左侧else if (precursor->_val.first > val.first){cur->_parent = precursor;precursor->_left = cur;}//连接在前驱指针右侧else{cur->_parent = precursor;precursor->_right = cur;}//更新平衡因子while (precursor){if (precursor->_left == cur)precursor->_bf--;elseprecursor->_bf++;//为0说明平衡了,不需要再更新了if (precursor->_bf == 0)break;//出现异常需要更新平衡因子,更新完就可以else if (precursor->_bf == 2 || precursor->_bf == -2){if (precursor->_bf == 2 && cur->_bf == 1){RotateL(precursor);}else if (precursor->_bf == -2 && cur->_bf == -1){RotateR(precursor);}else if (precursor->_bf == 2 && cur->_bf == -1){RotateRL(precursor);}else if (precursor->_bf == -2 && cur->_bf == 1){RotateLR(precursor);}else{assert(false);}//更新完平衡了不需要向上更新break;}//进行迭代(向上更新)cur = precursor;precursor = precursor->_parent;}return true;}//查找Node* Find(const K& key){//从根节点开始Node* cur = _root;while (cur){//大了就去左子树中搜索if (cur->_val.first > key){cur = cur->_left;}//小了就去右子树中搜索else if (cur->_val.first < key){cur = cur->_right;}else{//找到返回当前节点return cur;}}return nullptr;}//删除bool Erase(const K& key){//从根节点开始搜索Node* cur = _root;//作为cur的前驱指针Node* precursor = nullptr;//搜索查找while (cur){if (cur->_val.first > key){precursor = cur;cur = cur->_left;}else if (cur->_val.first < key){precursor = cur;cur = cur->_right;}elsebreak;}//找不到if (cur == nullptr) return false;//假设cur左右节点都存在,找右边最小值替换if (cur->_left != nullptr && cur->_right != nullptr){Node* tmp1 = cur->_right;Node* tmp2 = nullptr;while (tmp1->_left){tmp2 = tmp1;tmp1 = tmp1->_left;}cur->_val = tmp1->_val;//tmp1左边没有节点,自己就是最小的节点if (tmp2 == nullptr){precursor = cur;cur = tmp1;	}else{cur = tmp1;precursor = tmp2;}}//假设左边为空和左右节点都为空int sign = 0;//左边为-1,右边为1Node* deletecur = cur;if (cur->_left == nullptr){左边为空,父节点为空,cur为根节点,让cur->_riggt做为根,直接结束就行了(cur->_riggt本身为平衡树)if (precursor == nullptr){_root = cur->_right;delete deletecur;return true;}else{if (precursor->_left == cur){precursor->_left = cur->_right;if (cur->_right == nullptr) sign = -1;}else{precursor->_right = cur->_right;if (cur->_right == nullptr) sign = 1;}}cur = cur->_right;}//假设右边为空else{//右边为空,父节点为空,cur为根节点,让cur->_left做为根,直接结束就行了(cur->_left本身为平衡树)if (precursor == nullptr){_root = cur->_left;delete deletecur;return true;}else{if (precursor->_left == cur)precursor->_left = cur->_left;elseprecursor->_right = cur->_left;}cur = cur->_left;}//更新平衡因子while (precursor){//cur出现空的情况if (cur == nullptr){if (sign == -1)precursor->_bf++;elseprecursor->_bf--;}else if (precursor->_left == cur)precursor->_bf++;elseprecursor->_bf--;if (precursor->_bf == -1 || precursor->_bf == 1)break;else if (precursor->_bf == -2 || precursor->_bf == 2){//右边高了左单旋if (precursor->_bf == 2 && (precursor->_right->_bf == 0 || precursor->_right->_bf == 1)){//R会做为新的precursor先保存Node* R = precursor->_right;int bf = precursor->_right->_bf;RotateL(precursor);//在旋转后的平衡因子不符合预期,需要更新if (bf == 0){precursor->_bf = 1;R->_bf = -1;}//更新precursor = R;}//左边高了右单旋else if (precursor->_bf == -2 && (precursor->_left->_bf == 0 || precursor->_left->_bf == -1)){//L会做为新的precursor先保存Node* L = precursor->_left;int bf = L->_bf;RotateR(precursor);//在旋转后的平衡因子不符合预期,需要更新if (bf == 0){precursor->_bf = -1;L->_bf = 1;}//更新precursor = L;}else if (precursor->_bf == 2 && precursor->_right->_bf == -1){//L会做为新的precursor先保存Node* R = precursor->_right;Node* L = R->_left;RotateRL(precursor);//更新precursor = L;}else if (precursor->_bf == -2 && precursor->_left->_bf== 1){//R会做为新的precursor先保存Node* L = precursor->_left;Node* R = L->_right;RotateLR(precursor);//更新precursor = R;}elseassert(false);//旋转完precursor更新了再次判断是否平衡了if (precursor->_bf == -1 || precursor->_bf == 1) break;}//进行迭代cur = precursor;precursor = precursor->_parent;}delete deletecur;return true;}//判断是否为平衡树bool IsBalanceTree(){return _IsBalanceTree(_root);}private:// 右单旋void RotateR(Node* parent){//左节点Node* L = parent->_left;//左子树右边第一个节点Node* Lr = L->_right;//parent的父亲Node* pparent = parent->_parent;//连接过程L->_right = parent;parent->_parent = L;//该节点可能为空if (Lr){Lr->_parent = parent;}parent->_left = Lr;//更新L的父节点L->_parent = pparent;//是根的情况if (pparent == nullptr){_root = L;}else{if (parent == pparent->_left) pparent->_left = L;else pparent->_right = L;}//更新后平衡因子都为0parent->_bf = L->_bf = 0;}//左旋转void RotateL(Node* parent){//右边第一个节点Node* R = parent->_right;//右子树第一个左节点Node* Rl = R->_left;//父节点Node* pparent = parent->_parent;//连接过程parent->_right = Rl;if (Rl){Rl->_parent = parent;}R->_left = parent;//更新parent的父节点parent->_parent = R;//更新R的父节点R->_parent = pparent;//是根的情况if (nullptr == pparent){_root = R;}else{if (pparent->_left == parent) pparent->_left = R;else pparent->_right = R;}//更新平衡因子parent->_bf = R->_bf = 0;}// 右左双旋void RotateRL(Node* parent){Node* R = parent->_right;Node* Rl = R->_left;//先保存平衡因子int bf = Rl->_bf;//右左双旋RotateR(R);RotateL(parent);//更新平衡因子if (bf == 1){parent->_bf = -1;R->_bf = 0;Rl->_bf = 0;}else if (bf == -1){parent->_bf = 0;R->_bf = 1;Rl->_bf = 0;}else if (bf == 0){parent->_bf = 0;R->_bf = 0;Rl->_bf = 0;}elseassert(false);}// 左右双旋void RotateLR(Node* parent){Node* L = parent->_left;Node* Lr = L->_right;//先保存Lr的平衡因子,因为旋转之后Lr的平衡因子会变int bf = Lr->_bf;//左右双旋RotateL(L);RotateR(parent);//更新平衡因子if (bf == -1){parent->_bf = 1;L->_bf = 0;Lr->_bf = 0;}else if (bf == 1){parent->_bf = 0;L->_bf = -1;Lr->_bf = 0;}else if (bf == 0){parent->_bf = 0;L->_bf = 0;Lr->_bf = 0;}elseassert(false);}//求树的高度int _Height(Node* root){if (root == nullptr) return 0;int l = _Height(root->_left);int r = _Height(root->_right);return max(l, r) + 1;}//验证是否是平衡树bool _IsBalanceTree(Node* root){if (root == nullptr) return true;int l = _Height(root->_left);int r = _Height(root->_right);int buff = r - l;if (root->_bf != buff || buff > 1 || buff < -1)return false;return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);}void DeleteTree(Node* root){if (root == nullptr)return;DeleteTree(root->_left);DeleteTree(root->_right);delete root;}//根节点Node* _root = nullptr;
};

三、AVL树的性能

1、搜索性能分析
时间复杂度:O(log n)

(1) AVL树作为二叉搜索树的一种,其搜索操作与普通的二叉搜索树相同。从根节点开始,根据要搜索的值与节点值的大小关系,决定是向左子树搜索还是向右子树搜索,直到找到目标节点或搜索到叶子节点为止。
(2)由于AVL树的高度被限制在O(log n)以内(其中n是树中节点的数量),因此搜索操作的时间复杂度也是O(log n)。

2、插入性能分析
时间复杂度:O(log n)

(1)插入操作首先执行普通的二叉搜索树插入操作,找到新节点应该插入的位置。 插入新节点后,从该节点开始向上遍历,更新所有受影响节点的平衡因子。
(2)如果某个节点的平衡因子变为2或-2(即左右子树高度差超过1),则需要进行旋转操作来恢复平衡。旋转操作包括单旋转(左单旋、右单旋)和双旋转(左右双旋、右左双旋)。
(3)由于旋转操作只在从插入节点到根节点的路径上进行,且路径长度不超过树的高度,因此插入操作的时间复杂度也是O(log n)。

3、删除性能分析
时间复杂度:O(log n)

(1)删除操作首先找到要删除的节点。
(2)如果要删除的节点有两个子节点,则通常使用其右子树中的最小节点(或左子树中的最大节点)来替换它,并删除那个最小(或最大)节点。
(3)删除节点后,从该节点开始向上遍历,更新所有受影响节点的平衡因子。
(4)如果某个节点的平衡因子变为2或-2,则需要进行旋转操作来恢复平衡。旋转操作的类型与插入操作类似,包括单旋转和双旋转。
(5)同样地,由于旋转操作只在从删除节点到根节点的路径上进行,且路径长度不超过树的高度,因此删除操作的时间复杂度也是O(log n)。

4、总结

AVL树通过保持树的平衡性,使得搜索、插入和删除操作的时间复杂度都能保持在O(log
n)的水平。这种高效的性能使得AVL树在需要频繁进行动态修改操作的数据结构中非常有用,如数据库索引、文件系统等。然而,需要注意的是,AVL树在每次插入或删除操作后都需要进行平衡调整,这可能会增加一些额外的开销。但在大多数情况下,这种开销是可以接受的,因为AVL树提供了比未平衡的二叉搜索树更好的性能保证。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/876258.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue 状态管理 Vue CLI

Vue 状态管理 & Vue CLI 1、状态管理2、集中状态管理2.1 Vuex2.1.1 Vuex核心概念2.1.2 Vuex Store实例2.1.3 Vuex Getter2.1.4 Vuex Mutation2.1.4 Vuex Actions2.1.4 Vuex Module 2.2 Pinia2.2.1功能增强 3、Vuex 实现原理4、Pinia 实现原理5、CLI5.1 实现 1、状态管理 将…

【CG】计算机图形学(Computer Graphics)基础(其贰)

0 学习视频 B站GAMES101-现代计算机图形学入门-闫令琪 ※ 接上文【CG】计算机图形学&#xff08;Computer Graphics&#xff09;基础&#xff08;其壹&#xff09; 7 光线追踪 7.1 为什么需要光线追踪&#xff1f; 光栅化无法妥善处理全局效果 &#xff08;软&#xff09;阴…

一天搞定React(5)——ReactRouter(下)【已完结】

Hello&#xff01;大家好&#xff0c;今天带来的是React前端JS库的学习&#xff0c;课程来自黑马的往期课程&#xff0c;具体连接地址我也没有找到&#xff0c;大家可以广搜巡查一下&#xff0c;但是总体来说&#xff0c;这套课程教学质量非常高&#xff0c;每个知识点都有一个…

MATLAB基础:函数与函数控制语句

今天我们继续学习Matlab中函数相关知识。 API的查询和调用 help 命令是最基本的查询方法&#xff0c;可查询所有目录、指定目录、命令、函数。 我们直接点击帮助菜单即可查询所需的API函数。 lookfor 关键字用于搜索相关的命令和函数。 如&#xff0c;我们输入lookfor inpu…

开源物联网网关ThingsBoard IoT Gateway

前几天测试了Neuron&#xff0c;这是一个令人印象深刻的平台&#xff0c;不过它不能算是完全免费的平台&#xff0c;因为它还是有商业许可要求的&#xff0c;挺贵的&#xff0c;大几万的&#xff0c;而且它有走向闭源的趋势。所以也在寻找它的替代方案。 今天看到一个ThingsBo…

Django项目中报错:django.template.exceptions.TemplateDoesNotExist: index.html

访问127.0.0.1&#xff1a;8000访问出错 查看报错原因 到Django项目当中找到settings.py&#xff0c;找到TEMPLATES中的DIRS: 添加如下代码&#xff0c;并导入OS模块&#xff1a; "DIRS": [os.path.join(BASE_DIR,templates)] 再次访问IP地址&#xff1a;

C++(入门1)

C参考文档 Reference - C Reference C 参考手册 - cppreference.com cppreference.com 第一个C程序 #include<stdio.h> int main() {printf("Hello C\n");return 0; }由上述代码可知C是兼容C语言 第一个C标准程序 #include<iostream> using names…

【机器学习】智驭未来:机器学习如何重塑制造业的转型与升级

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀目录 &#x1f50d;1. 引言&#x1f4d2;2. 机器学习重塑制造业生产流程&#x1f338;预测性维护&#xff1a;减少停机时间&#xff0c;提高设…

实现共模噪声电流相互抵消的方法

共模传导路径中噪声电流相互抵消&#xff0c;从而使总的共模电流减小&#xff0c; 终达到降噪的目的。目前为实现共模噪声电流相互抵消&#xff0c;主要是采用动点电容抵消法。 动点电容抵消法原理 动点电容抵消法就是选取合适的动点&#xff0c;添加原副边跨接电容&#xff0c…

黑马头条Day10-定时计算热点文章、xxl-job

一、今日内容 1. 需求分析 目前实现的思路&#xff1a;从数据库直接按照发布时间倒序查询 问题&#xff1a; 如果访问量比较大&#xff0c;直接查询数据库&#xff0c;压力较大新发布的文章会展示在前面&#xff0c;并不是热点文章 2. 实现思路 解决方案&#xff1a;把热点…

CCS(Code Composer Studio 10.4.0)编译软件中文乱码怎么解决

如果是所有文件都出现了中文乱码这时建议直接在窗口首选项中修改&#xff1a;选择"Window" -> "Preferences"&#xff0c;找到"General" -> "Workspace"&#xff0c;将"Text file encoding"选项设置为"Other&quo…

深度解析Linux-C——函数和内存管理

目录 函数指针&#xff1a; 指针函数&#xff1a; 参数为指针的函数&#xff1a; 参数为数组的函数&#xff1a; C语言内存管理 stdlib.h头文件常用函数介绍 1、局部变量 2、全局变量 3、 堆空间变量 4、静态变量 5、常量 函数指针&#xff1a; 指向函数的指针&#…

Linux文件与相关函数的知识点3

main函数参数 int main(int argc,char *argv[]) { return 0; } C语言规定了main函数的参数只能有两个&#xff0c;一个是argc,一个是argv并且&#xff0c;argc只能是整数&#xff0c;第二个必须是指向字符 串的指针数组。 argc: 参数表示命令行中参数的个数&#xff0…

Java实现七大排序(二)

一.交换排序 1.冒泡排序 这个太经典了&#xff0c;每个学编程都绕不开的。原理跟选择排序差不多&#xff0c;不过冒泡排序是直接交换。 public static void bubbleSort(int[] array){for (int i 0; i < array.length - 1; i) {for (int j 0; j < array.length-1-i; j…

域内攻击手法——AS-REP Roasting攻击和Kerberoasting攻击

一、AS-REP Roasting攻击 1、AS-REP Roasting攻击原理 AS-REP Roasting是一种对用户账户进行离线爆破的攻击方式。但是该攻击方式使用上比较受限&#xff0c;因为其需要用户账户设置不要求Kerberos 预身份验证选项&#xff0c;而该选项默认是没有勾选的。Kerberos 预身份验证…

20240727 每日AI必读资讯

&#x1f310;OpenAI向Google宣战&#xff0c;重磅推出AI搜索引擎SearchGPT &#xff01; - 将 AI 与实时网络信息结合 提供生成式UI结果 - SearchGPT 结合网络最新信息可以直接回答问题&#xff0c;同时注明相关来源链接。 - 还可以像与人对话一样提出后续问题&#xff0c;…

进程概念(三)----- fork 初识

目录 前言1. pid && ppid2. forka. 为什么 fork 要给子进程返回 0&#xff0c; 给父进程返回子进程的 pid &#xff1f;b. 一个函数是如何做到两次的&#xff1f;c. fork 函数在干什么&#xff1f;d. 一个变量怎么做到拥有不同的内容的&#xff1f;e. 拓展&#xff1a;…

小红书电商首提“生活方式电商”定义,个性化需求也能做成好生意

近日&#xff0c;小红书发布COO柯南与经济学者薛兆丰的对谈视频。对谈中柯南首次对外定义&#xff0c;小红书电商是“生活方式电商”。 柯南表示&#xff0c;生活方式电商是让用户在小红书买到的&#xff0c;不仅是好产品&#xff0c;也是一种向往的生活。 随着生活方式的多元…

【初阶数据结构】9.二叉树(4)

文章目录 5.二叉树算法题5.1 单值二叉树5.2 相同的树5.3 另一棵树的子树5.4 二叉树遍历5.5 二叉树的构建及遍历 6.二叉树选择题 5.二叉树算法题 5.1 单值二叉树 点击链接做题 代码&#xff1a; /*** Definition for a binary tree node.* struct TreeNode {* int val;* …

PID 控制实验 - 整定实验

Arduino PID Arduino-PID-LibraryArduino-PID-AutoTune-Library PID控制实验 – 制作测试台 PID Control Experiment – Making the Testing Rig PID (Proportional, Integral, Derivative) control is a classic control algorithm that I have used for a few projects,…