目录
引言
AVL树的概念
AVL树节点的定义
AVL树的插入
AVL树的基本结构
AVL树的插入
第一步:按搜索树的规则进行插入
第二步:更新平衡因子
1、父节点的平衡因子为 parent->bf == 0
2、更新完 parent 的 bf,如果 parent->bf == 1 或者 -1,说明 parent 的高度变了,继续往上更新
3、更新完 parent 的 bf,如果 parent->bf == 2 或者 -2,说明 parent 所在的子树出现了不平衡,需要旋转处理
更新平衡因子的代码实现:
AVL树的旋转
旋转的几种形态
左单旋
右单旋
左右双旋
右左双旋
单旋的实现
左单旋
具象图
抽象图
左单旋代码实现
右单旋
具象图
抽象图
右单旋代码实现
双旋的实现
左右双旋
具象图
抽象图
左右双旋代码实现
右左双旋
具象图
抽象图
AVL树插入的完整代码
AVL树插入的验证
AVLTree.h
Test.cpp
测试结果
AVL树的删除
AVL树删除基本结构
更新平衡因子
1.删除节点整颗树的高度不变
1.1父节点的平衡因子变成2,父亲的右孩子平衡因子为0
1.2父节点的平衡因子变成2,父亲的右孩子平衡因子为1
1.3父节点的平衡因子变成 -2,父亲的左孩子平衡因子为0
1.4父节点的平衡因子变成 -2,父亲的左孩子平衡因子为-1
2.删除节点整棵树的高度改变
2.1父节点的平衡因子变成 2,父亲的右孩子平衡因子为-1,subRL->bf ==1
2.2父节点的平衡因子变成 2,父亲的右孩子平衡因子为-1,subRL->bf ==-1
2.3父节点的平衡因子变成 -2,父亲的左孩子平衡因子为1,subLR->bf ==1
2.4父节点的平衡因子变成 -2,父亲的左孩子平衡因子为1,subLR->bf == -1
删除的完整代码
AVL树插入与删除的完整代码
AVLTree.h
test.cpp
AVL树的性能
引言
二叉搜索树(Binary Search Tree, BST)是一种常用的数据结构,它可以有效地支持动态集合上的各种基本操作,如插入、删除和查找。在理想情况下,二叉搜索树的平均查找时间复杂度为O(log n),这大大优于线性搜索的O(n)时间复杂度。然而,如果插入的数据是有序的或接近有序的,二叉搜索树可能会退化成链状结构(即单支树),这时查找效率会下降到O(n),与简单的顺序列表相当。
为了解决这一问题,两位俄罗斯数学家G.M. Adelson-Velskii和E.M. Landis在1962年提出了一种改进的方法,这就是后来广为人知的AVL树。AVL树是一种自平衡二叉搜索树,其特点是每个节点的左右子树的高度差的绝对值不超过1。当向AVL树中插入或删除节点后,如果破坏了这种平衡条件,AVL树会通过一系列的旋转操作自动调整,以确保整个树始终保持平衡状态。这样,即使在极端情况下,AVL树也能维持较低的高度,从而保证平均查找长度为O(log n),提高了查找效率。
AVL树的概念
AVL树的概念:
一棵AVL树是一种特殊的二叉搜索树,它不仅满足二叉搜索树的性质(即左子树的所有节点的值小于根节点的值,右子树的所有节点的值大于根节点的值),还满足以下两个关键特性:
左右子树都是AVL树:这意味着每个子树也是平衡的,并且也满足AVL树的所有属性。
左右子树高度之差的绝对值不超过1:每个节点的平衡因子(Balance Factor, BF)定义为其左子树的高度减去右子树的高度。在AVL树中,任意节点的平衡因子只能是-1、0或1。
AVL树的平衡因子:
- 右子树高度减去左子树高度:
- 平衡因子 BF = hRight − hLeft
- 在这种定义下,如果 BF==0,表示左右子树高度相同;如果 BF==1,表示右子树比左子树高;如果 BF==−1,表示左子树比右子树高。
- 左子树高度减去右子树高度:
- 平衡因子 BF = hLeft−hRight
- 在这种定义下,如果 BF==0,表示左右子树高度相同;如果 BF==1,表示左子树比右子树高;如果 BF==−1,表示右子树比左子树高。
- 这两种定义方式在本质上是等价的: 只是符号相反。无论采用哪种定义方式,只要保持统一的计算标准即可。
AVL树的高度:
- 高度平衡:AVL树通过保持每个节点的左右子树高度差不超过1来保证树的高度平衡。这种平衡性确保了树的高度始终保持在O(log n)的范围内。
AVL树节点的定义
在学习
set
和map
的使用时,我们了解到std::pair
是一个模板类,它包含两个参数,一个用于存储键(key
),另一个用于存储值(value
)。在本篇文章中,我们将实现一个key-value
型的 AVL 树。我们直接使用 C++ 标准库中的std::pair
来存储键值对,我们重点是实现 AVL 树的插入、删除和旋转操作。
template<class K, class V>
struct AVLTreeNode
{AVLTreeNode<K, V>* _left; //该节点的左孩子AVLTreeNode<K, V>* _right; //该节点的右孩子AVLTreeNode<K, V>* _parent; //该节点的父亲int _bf; //balance factor 平衡因子pair<K, V> _kv; //节点里面的值 key-value模型AVLTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _bf(0){}
};
AVL树的插入
AVL树的基本结构
我们已经定义了AVL树的节点,现在可以把这个基本的架子搭建起来,然后再实现插入和删除操作。
#pragma once
template<class K, class V>
struct AVLTreeNode
{AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;int _bf; //balance factor 平衡因子pair<K, V> _kv;AVLTreeNode(const pair<K, V>& kv) :_left(nullptr),_right(nullptr),_parent(nullptr),_bf(0),_kv(kv){}
};
template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public:bool Insert(const pair<K, V>& kv){}
private:Node* _root = nullptr;
};
AVL树的插入
AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么AVL树的插入过程可以分为两步:
- 按照二叉搜索树的方式插入新节点 C++ 二叉树进阶:二叉搜索树-CSDN博客
- 调整节点的平衡因子
第一步:按搜索树的规则进行插入
bool Insert(const pair<K, V>& kv) 如果你希望
Insert
方法返回新插入节点的信息,你可以选择返回pair<iterator,bool>
或者直接返回Node*
,但这并不是常见的做法。通常,Insert
方法只关心插入是否成功,并在内部处理所有平衡操作。三叉链的主要作用是为后面更新平衡因子起着重要的作用,方便往回找节点。
bool Insert(const pair<K, V>& kv){//1,先按搜索树的规则进行插入if (_root == nullptr){_root = new Node(kv);return true;}Node* parent = nullptr;Node* cur = _root;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 (parent->_kv.first < kv.first){parent->_right = cur;cur->_parent = parent; //每次插入新的节点都把它的parent指向它的父节点,形成三叉链}else{parent->_left = cur;cur->_parent = parent; //每次插入新的节点都把它的parent指向它的父节点,形成三叉链}return true;}
第二步:更新平衡因子
二叉搜索树面临的问题是什么???
如果极端的情况下,树高会越来越高,我怎么知道树出问题了,我们选择的方式是引入平衡因子,并不是一定要平衡因子,这只是一种方式。
平衡因子就是用来衡量这棵树有没有出现问题,当平衡因子没有超过 1 的或者 -1的时候这颗树是正常的,当平衡因子>= 2 的绝对值的时候,我们就需要降低树的高度,即通过旋转来实现(这里我们采用右子树的高度减去左子树的高度计算平衡因子)。
一个节点的插入,只会影响他的祖先,因为没有动他的孩子。
再进一步分析,也不是所有的祖先都会被影响
新增的节点的平衡因子肯定是0,因为左右子树是nullptr,但是我会影响我父亲
如果新节点插入到父节点的左子树位置(即
cur
是parent
的左子节点),按照右子树高度减去左子树高度来计算平衡因子的规则,父节点的左子树高度增加了 1(因为多了一个新节点),那么父节点的平衡因子就需要减 1,也就是parent->bf--
。反之,如果新节点插入到父节点的右子树位置(即
cur
是parent
的右子节点),父节点的右子树高度增加了 1,此时父节点的平衡因子就需要加 1,即parent->bf++
。即:当 cur 是 parent 的左,parent->bf--
当 cur 是 parent 的右,parent->bf++
注意:
- 平衡因子的更新会沿着从被插入节点的父节点一直到根节点的路径上的所有节点产生影响。这是因为每个节点的平衡因子是基于其左右子树的高度差来计算的,当在树中插入或删除一个节点时,从该节点到根节点路径上的每个节点的左右子树高度关系可能发生改变,从而导致平衡因子需要重新计算和更新。
- 这就是为什么这里使用三叉链的原因,因为我们可以迭代向上去更新父节点以及祖父的平衡因子。
1、父节点的平衡因子为 parent->bf == 0
更新完parent的 bf 之后,如果 parent->bf == 0, 说明parent高度不变,更新结束,插入完成。
解释:说明更新前,parent 的 bf 是 1 或者 -1,现在变成0,说明把矮的那边填上了。说明我的高度不变,对上层没有影响。
2、更新完 parent 的 bf,如果 parent->bf == 1 或者 -1,说明 parent 的高度变了,继续往上更新
说明在更新前,parent 的 bf 是 0,现在变成的 1 或者 -1 说明变高了,即祖父节点的子树高度也会变高,对上层有影响,所以需要继续往上面更新。
3、更新完 parent 的 bf,如果 parent->bf == 2 或者 -2,说明 parent 所在的子树出现了不平衡,需要旋转处理
只可能存在这几种情况,不可能存在3 或者 -3,因为当平衡因子是 2 或者 -2 的时候就被处理了。
更新平衡因子的代码实现:
只要 parent 还存在就继续往上面更新平衡因子,直到 parent 为空。
旋转我们还没有实现,所以我们下面继续往下分析旋转,旋转处理完之后,更新平衡因子就可以完整实现了。
while (parent)
{if (cur == parent->_right)parent->_bf++;elseparent->_bf--;if (parent->_bf == 0) {//说明parent所在子树的高度不变,更新结束break;}else if(parent->_bf == 1 || parent->_bf == -1) {//说明 parent 所在子树的高度变了,继续往上更新,利用的是三叉链继续往上更新cur = parent;parent = parent->_parent;}else if (parent->_bf == 2 || parent->_bf == -2) {//parent所在子树出现不平衡了,需要旋转处理}
}
AVL树的旋转
旋转的几种形态
我们在学习AVL树的旋转之前,我们先来看一下是如何完成旋转的,我们先看几个示例,下来还会详细说明其规律。
旋转的形态主要包括:左单旋(RR)、右单旋(LL)、左右单旋(LR)、右左单旋(RL)。
有了这些作为铺垫,也更好理解下面的抽象图,旋转的实现会相对轻松。
左单旋
为什么图二中subRL作为parent的右子树,因为它比subR小,又比parent大,而且各子树都满足二叉搜索树,中序是有序的
就是 15 这个节点比 20 这个节点小,但是比 10 这个节点大,所以链接在10的右边
注意:这里所表示把左边或者右边往下按,是方便理解,其实旋转的本质是改变节点指针的指向
图中失衡的情形也叫做 RR型失衡,因为在失衡点10的右树的右孩子插入的节点,RR型需要进行左单旋,其实我们不需要管那种形态失衡,直接观察哪变高就知道如何旋转了。
右单旋
和左单旋同理,图二中的subRL节点25链接在parent节点30的左边,因为subL节点20 比节点30 parent 小,而节点25 subRL 比节点20 subL大,节点25 subLR 比 节点30 parent小,所以链接在 parent节点30 的左边。
注意:图中失衡的情形也叫做 LL型失衡,因为在失衡点30的左树的左孩子插入的节点,LL型需要进行右单旋。
左右双旋
左右双旋也叫 LR 型失衡,先对失衡点的左子树进行左单旋,再对失衡点进行右单旋
右左双旋
右左双旋也叫 RL 型失衡,先对失衡点的右子树进行右单旋,再对失衡点进行左单旋
我们已经知道了是属于哪种失衡了,那么我们可以来看几个例子:
LL型失衡->右单旋
RR型失衡->左单旋
注意:假设插入的不是80,而是69.5,那么插入在70的左孩子,可能会误以为是RL型,其实还是RR型,因为形态的确认是从失衡点开始,右子树R,右孩子R,右孩子左右插入都不影响它是RR型。
RL型失衡->右左双旋
LR型失衡->左右双旋
单旋的实现
LL RR RL LR 四种形态,先看简单的左/右单旋
不一定就是这棵树,也可能是一棵树的部分子树
左单旋
具象图
这只是其中的两种情况,树还会更复杂,它的旋转都是一样的,针对这些我们就可以给出左单旋中所有情况的抽象图
抽象图
左单旋:
1.subR的左边给了 parent 的右边
2.parent 变成了 subR 的左边
3.subR 变成了树的根
左单旋代码实现
首先我们得来思考,下面的代码正确吗???
为了更新平衡因子,有了_parent,所以旋转也需要调整_parent,下面代码问题就是没有处理每个节点的_parent。
//左单旋
void RotateL(Node* parent)
{Node* subR = parent->_right;parent->_right = subR->_left;subR->_left = parent;}
纠正后的代码
①先看 b 子树还有可能为空,所以我们需要判断subRL是否为空。
②当我们处理的是一个颗树的子树的时候,找到了parent的父节点ppNode,这个时候需要链接,也需要判断原来的parent是属于ppNode的哪一边。
为什么最后需要把 parent 和 subR 的平衡因子变成 0 ???
因为只动了parent 和 subR 的孩子,其它节点没有影响,仅仅改变了parent 和subR 的平衡因子。
开始肯定是平衡树,插入操作变成不平衡就立马完成旋转之后还是平衡树。
//左单旋void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;subR->_left = parent;Node* ppNode = parent->_parent; //在父节点的parent指向改变之前先保持其parent的父节点parent->_parent = subR;//1,parent是原来这棵树的根,现在subR是根//2,parent 为根的树只是整颗树中的子树,改变链接关系,那么subR就要顶替它的位置if (_root == parent){_root = subR;subR->_parent = nullptr;}else{if (ppNode->_left == parent)ppNode->_left = subR;elseppNode->_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;subL->_right = parent;Node* ppNode = parent->_parent;parent->_parent = subL;if (_root == parent){_root = subL;subL->_parent = nullptr;}else{if (ppNode->_left == parent)ppNode->_left = subL;elseppNode->_right = subL;subL->_parent = ppNode;}subL->_bf = parent->_bf = 0;
}
双旋的实现
双旋是由两个单旋构成的。
左右双旋
具象图
这个是具象图中的一种特殊情况,处理完之后,最后的平衡因子都变成了 0。
通常情况下,它都会存在子树,并且它这棵树的90不一定是根节点,而是另外一颗树的子树。(右左双旋在这种特殊情况下也是类似的)。
我们已经理解了左单旋和右单旋的抽象图,在左右双旋的抽象图中,我们就能更好的理解左右双旋的一般情况。
抽象图
在左右双旋的情况下,我们观察图形,可以发现这种情况属于一条折线,又属于LR型,前面说过,导致失衡是看是失衡点的孩子的左右子树,和它插入b子树或者c子树的左右无关系,但是平衡因子会受到影响,所以我们需要考虑平衡因子的情况下(右左双旋也是同理的)。
左右双旋情况下失衡点的平衡因子是 -2
①左右双旋的情况下,当subLR的平衡因子是 1 的时候,旋转完成之后
subLR的平衡因因子变成了0
parent的平衡因子变成了0
subL的平衡因子变了了-1②左右双旋的情况下,当subLR的平衡因子是 -1 的时候,旋转完成之后
subLR的平衡因因子变成了0
parent的平衡因子变成了1
subL的平衡因子变了了0③左右双旋的情况下,当subLR的平衡因子是0的时候,也就是具象图中的情况下,最后的平衡因子都是0, 这是一种特殊情形
综上:
①subLR->bf == 1的时候
parent->bf == 0
subL->bf == -1
subLR->bf == 0
②subLR->bf == -1的时候
parent->bf == 1
subL->bf == 0
subLR->bf == 0
③subLR->bf == 0的时候
parent->bf == 0
subL->bf == 0
subLR->bf == 0
在c子树插入新的节点
在b子树插入新的节点
左右双旋代码实现
//左右双旋
void RotateLR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf; //先记录这个平衡因子,因为会发生旋转,平衡因子就会改变,没改变之前先记录RotateL(subL); //左单旋,一棵子树左单旋会自动处理好本身这颗子树与父亲的关系,也就是会链接起来,前面我们已经实现了RotateR(parent); //右单旋同理if (bf == 1) {parent->_bf = 0;subL->_bf = -1;subLR->_bf = 0;}else if (bf == -1){parent->_bf = 1;subL->_bf = 0;subLR->_bf = 0;}else if (bf == 0){parent->_bf = 0;subL->_bf = 0;subLR->_bf = 0;}
}
右左双旋
具象图
抽象图
在右左双旋的情况下,我们观察图形,可以发现这种情况属于一条折线,又属于RL型,前面说过,导致失衡是看是失衡点的孩子的左右子树,和它插入b子树或者c子树的左右无关系,但是平衡因子会受到影响,所以我们还是需要考虑平衡因子的情况
右左双旋情况下失衡点的平衡因子是 2
①右左双旋的情况下,当subRL的平衡因子是 1 的时候,旋转完成之后
subRL的平衡因因子变成了 0
parent的平衡因子变成了 -1
subR的平衡因子变了了 0②右左双旋的情况下,当subRL的平衡因子是 -1 的时候,旋转完成之后
subRL的平衡因因子变成了 0
parent的平衡因子变成了 0
subR的平衡因子变了了 1③右左双旋的情况下,当subRL的平衡因子是 0 的时候,也就是具象图中的情况下,最后的平衡因子都是0, 这是一种特殊情形
综上:
①subRL->bf == 1的时候
parent->bf == -1
subR->bf == 0
subRL->bf == 0
②subRL->bf == -1的时候
parent->bf == 0
subR->bf == 1
subRL->bf == 0
③subRL->bf == 0的时候
parent->bf == 0
subR->bf == 0
subRL->bf == 0
在c子树插入新的节点
在b子树插入新的节点
右左双旋代码实现
//右左双旋
void RotateRL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(parent->_right);RotateL(parent);if (bf == -1){parent->_bf = 0;subR->_bf = 1;subRL->_bf = 0;}else if (bf == 1){subR->_bf = 0;parent->_bf = -1;subRL->_bf = 0;}else if (bf == 0){subR->_bf = 0;parent->_bf = 0;subRL->_bf = 0;}
}
AVL树插入的完整代码
我们已经把旋转实现了,现在可以把插入完整的实现了。
①左右双旋情况下失衡点的平衡因子是 -2
②右左双旋情况下失衡点的平衡因子是 2
为什么旋转完成之后就可以跳出循环呢???
假设parent是根节点,旋转完成之后,已经变得平衡了,不需要往上更新。
假设是一棵树的子树,插入节点之前肯定是平衡树,开始高度是h+2,插入之后h+3,旋转之后还是h+2,所以和原来一样,高度没变,所以不需要往上更新了,也可以这样理解,原来就是平衡树,现在插入之后高度变化了,但是经过调整又平衡了,所以也不需要调整。
旋转完成后,parent所在的树的高度恢复到了,插入节点前高度,如果是子树,对上层影响,更新结束。
bool Insert(const pair<K, V>& kv)
{//1,先按搜索树的规则进行插入if (_root == nullptr){_root = new Node(kv);return true;}Node* parent = nullptr;Node* cur = _root;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 (parent->_kv.first < kv.first){parent->_right = cur;cur->_parent = parent;}else{parent->_left = cur;cur->_parent = parent;}//2,更新平衡因子while (parent){if (cur == parent->_right){parent->_bf++;}else{parent->_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){//parent 所在的子树出现不平衡,需要旋转处理if (parent->_bf == 2){if (cur->_bf == 1) //右边高,都是正数->左单旋{RotateL(parent);}else if (cur->_bf == -1) //折线 -> 双旋{RotateRL(parent);}}else if (parent->_bf == -2){if (cur->_bf == -1) //都是负数,左边高->右单旋{RotateR(parent);}else if (cur->_bf == 1) //折线 -> 双旋{RotateLR(parent);}}break; //旋转我之后直接跳出循环}}return true;
}//左单旋
void RotateL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;subR->_left = parent;Node* ppNode = parent->_parent;parent->_parent = subR;//1,parent是原来这棵树的根,现在subR是根//2,parent 为根的树只是整颗树中的子树,改变链接关系,那么subR就要顶替它的位置if (_root == parent){_root = subR;subR->_parent = nullptr;}else{if (ppNode->_left == parent)ppNode->_left = subR;elseppNode->_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;subL->_right = parent;Node* ppNode = parent->_parent;parent->_parent = subL;if (_root == parent){_root = subL;subL->_parent = nullptr;}else{if (ppNode->_left == parent)ppNode->_left = subL;elseppNode->_right = subL;subL->_parent = ppNode;}subL->_bf = parent->_bf = 0;
}
//右左双旋
void RotateRL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(parent->_right);RotateL(parent);if (bf == -1){parent->_bf = 0;subR->_bf = 1;subRL->_bf = 0;}else if (bf == 1){subR->_bf = 0;parent->_bf = -1;subRL->_bf = 0;}else if (bf == 0){subR->_bf = 0;parent->_bf = 0;subRL->_bf = 0;}
}
//左右双旋
void RotateLR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(subL);RotateR(parent);if (bf == 1){parent->_bf = 0;subL->_bf = -1;subLR->_bf = 0;}else if (bf == -1){parent->_bf = 1;subL->_bf = 0;subLR->_bf = 0;}else if (bf == 0){parent->_bf = 0;subL->_bf = 0;subLR->_bf = 0;}
}
AVL树插入的验证
AVLTree.h
#pragma once
template<class K, class V>
struct AVLTreeNode
{AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;int _bf; //balance factor 平衡因子pair<K, V> _kv;AVLTreeNode(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){//1,先按搜索树的规则进行插入if (_root == nullptr){_root = new Node(kv);return true;}Node* parent = nullptr;Node* cur = _root;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 (parent->_kv.first < kv.first){parent->_right = cur;cur->_parent = parent;}else{parent->_left = cur;cur->_parent = parent;}//2,更新平衡因子while (parent){if (cur == parent->_right){parent->_bf++;}else{parent->_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){//parent 所在的子树出现不平衡,需要旋转处理if (parent->_bf == 2){if (cur->_bf == 1){RotateL(parent);}else if (cur->_bf == -1) //双旋{RotateRL(parent);}}else if (parent->_bf == -2){if (cur->_bf == -1){RotateR(parent);}else if (cur->_bf == 1) //双旋{RotateLR(parent);}}break;}}return true;}//左单旋void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;subR->_left = parent;Node* ppNode = parent->_parent;parent->_parent = subR;//1,parent是原来这棵树的根,现在subR是根//2,parent 为根的树只是整颗树中的子树,改变链接关系,那么subR就要顶替它的位置if (_root == parent){_root = subR;subR->_parent = nullptr;}else{if (ppNode->_left == parent)ppNode->_left = subR;elseppNode->_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;subL->_right = parent;Node* ppNode = parent->_parent;parent->_parent = subL;if (_root == parent){_root = subL;subL->_parent = nullptr;}else{if (ppNode->_left == parent)ppNode->_left = subL;elseppNode->_right = subL;subL->_parent = ppNode;}subL->_bf = parent->_bf = 0;}//右左双旋void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(parent->_right);RotateL(parent);if (bf == -1){parent->_bf = 0;subR->_bf = 1;subRL->_bf = 0;}else if (bf == 1){subR->_bf = 0;parent->_bf = -1;subRL->_bf = 0;}else if (bf == 0){subR->_bf = 0;parent->_bf = 0;subRL->_bf = 0;}}//左右双旋void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(subL);RotateR(parent);if (bf == 1){parent->_bf = 0;subL->_bf = -1;subLR->_bf = 0;}else if (bf == -1){parent->_bf = 1;subL->_bf = 0;subLR->_bf = 0;}else if (bf == 0){parent->_bf = 0;subL->_bf = 0;subLR->_bf = 0;}}void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_InOrder(root->_right);}void InOrder(){_InOrder(_root);}//判断是否平衡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;}bool _IsBalanceTree(Node* root){//空树也是AVL树if (root == nullptr)return true;//计算节点的平衡因子:即左右子树的高度差int leftHeight = height(root->_left);int rightHeight = height(root->_right);int diff = rightHeight - leftHeight;//如果计算出的平衡因子与该节点不相等,// 或者该节点的平衡因子的绝对值超过1,则一定不是AVL树if (abs(diff) >= 2){cout << root->_kv.first << "高度异常" << endl;return false;}if (root->_bf != diff){cout << root->_kv.first << "平衡因子异常" << endl;return false;}//该节点的左和右如果都是AVL树,则该树一定是AVl树return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);}bool IsBalance(){return _IsBalanceTree(_root);}
private:Node* _root = nullptr;
};void TestAVLTree()
{int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 }; //int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };//int a[] = { 5, 30, 70, 11, 2, 1, 18, 100, 10000 };AVLTree<int, int> t;for (auto e : a){t.Insert(make_pair(e, e));cout << e << "->"; cout << t.IsBalance() << endl;//每次插入节点检查是不是平衡的}t.InOrder();cout << t.IsBalance() << endl;cout << endl;}
Test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include "AVLTree.h"int main()
{TestAVLTree();return 0;
}
测试结果
AVL树的删除
avl 树的删除和插入类似,只不过情况有点不一样,在删除之后,平衡因子会发生变化,我们需要进行分类讨论
具体思路:删除的操作和二叉搜索树的删除是类似的,存在三种情况:
C++ 二叉树进阶:二叉搜索树-CSDN博客 这章都有详细介绍,是如何进行删除的。
①左为空
②右为空
③ 左右都不为空
第一步:按搜索树的规则进行删除。
第二步:更新平衡因子。
第三步,出现平衡因子为 2 或者 -2 的根据情况进行旋转处理。
注意:
虽然步骤是这样,但是删除的时候你会发现,当你把cur删除之后,如果先删除会导致一些问题,比如cur已经不在了再去访问程序就会崩,就算这个时候你没有释放cur,但是已经修改了链接关系,还是会导致树的高度有变化。
所以我们找到了,先更新平衡因子,再进行删除。
左为空,右为空我们找到的cur和它的父节点,而左右都不为空的时候,我们找到的是树的右子树的最左节点或者左子树的最右节点,以及它的父亲,如果像插入一样写代码就是错误了,这个时候我们可以把更新平衡因子封装成一个函数,专门用来更新,情况①②左或者右为空的时候,传入cur和父节点,进行更新平衡因子,而情况三,转换成删除叶子节点,传入右子树的最左节点和它的父节点,原理和情况①②类似,只不过节点和父亲不一样,进行更新平衡因子。
AVL树删除基本结构
在删除一个节点之后,节点之间的 _parent 链接关系可能被破坏了,我们需要链接_parent,和删除是一个原理的,删除之后也需要维持这种三叉链的关系。
bool Erase(const pair<K, V>& kv)
{Node* parent = nullptr;Node* cur = _root;while (cur){if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else{// 1、左为空// 2、右为空// 3、左右都不为空if (cur->_left == nullptr){if (cur == _root){_root = cur->_right;if (_root) //更新parent指向为nullptr_root->_parent = nullptr;}else{if (parent->_right == cur)parent->_right = cur->_right;elseparent->_left = cur->_right;if (cur->_right) //更新parentcur->_right->_parent = parent;}delete cur;}else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;if (_root)_root->_parent = nullptr;}else{if (parent->_left == cur)parent->_left = cur->_left;elseparent->_right = cur->_left;if (cur->_left)cur->_left->_parent = parent;}delete cur;}else{Node* rightMinParent = cur;Node* rightMin = cur->_right;while (rightMin->_left){rightMinParent = rightMin;rightMin = rightMin->_left;}//替代cur->_kv = rightMin->_kv;//转换成删除rightMin (rightMin是左为空,父亲指向它的右边)if (rightMin == rightMinParent->_left)rightMinParent->_left = rightMin->_right;elserightMinParent->_right = rightMin->_right;if (rightMin->_right) //链接parentrightMin->_right->_parent = rightMinParent;delete rightMin;}return true;}}return false;
}
更新平衡因子
①当父节点的平衡因子变成0的时候,那么刚刚开始要么是1或者-1,就是左边有节点或者右边有节点,现在把该节点删除之后,树的高度就发生了变化,对沿途路径的父及祖父节点会右影响,所以需要继续往上更新平衡因子。
②当父节点的平衡因子变成了1或者-1的时候,说明开始平衡因子是0,左右都有节点,删除任意一个之后,高度没有变化,对沿途路径的祖父节点没有任何的影响。
③当父节点的平衡因子变成了 2 或者 -2 的时候,树的高度出现不平衡,这个时候需要旋转处理。
综上:
当右边删除的时候,父亲的平衡因子减1,当左边删除的时候,父亲的平衡因子加1。
①当父亲的平衡因子变成 0 的时候,说明高度变了,继续往上更新。
②当父亲的平衡因子变成 -1 或者 1 的时候,说明高度没有变化,更新结束。
③当父亲的平衡因子变成 2 或者 -2 的时候,旋转处理。
①当平衡因子变成0的时候
②当平衡因子变成1或者-1的时候
1.删除节点整颗树的高度不变
1.1父节点的平衡因子变成2,父亲的右孩子平衡因子为0
当 parent->bf == 2, subR->bf == 0
左单旋之后更新平衡因子变成了 parent->bf == 1, subR->bf == -1
1.2父节点的平衡因子变成2,父亲的右孩子平衡因子为1
当 parent->bf == 2, subR->bf == 1
左单旋之后更新平衡因子变成了 parent->bf == 0, subR->bf ==0
1.3父节点的平衡因子变成 -2,父亲的左孩子平衡因子为0
当 parent->bf == -2, subL->bf == 0
左单旋之后更新平衡因子变成了 parent->bf == -1, subR->bf ==1
1.4父节点的平衡因子变成 -2,父亲的左孩子平衡因子为-1
当 parent->bf == -2, subL->bf == 0
左单旋之后更新平衡因子变成了 parent->bf == 0, subR->bf ==0
2.删除节点整棵树的高度改变
2.1父节点的平衡因子变成 2,父亲的右孩子平衡因子为-1,subRL->bf ==1
parent->bf == 2, subR->bf == -1,subRL->bf ==1
右左双旋之后更新平衡因子变成了 parent->bf == -1, subR->bf ==0,subRL->bf ==0
2.2父节点的平衡因子变成 2,父亲的右孩子平衡因子为-1,subRL->bf ==-1
parent->bf == 2, subR->bf == -1,subRL->bf ==-1
右左双旋之后更新平衡因子变成了 parent->bf == 0, subR->bf ==1,subRL->bf ==0
2.3父节点的平衡因子变成 -2,父亲的左孩子平衡因子为1,subLR->bf ==1
parent->bf == -2, subR->bf == 1,subLR->bf ==1
左右双旋之后更新平衡因子变成了 parent->bf == 0, subL->bf == -1,subLR->bf ==0
2.4父节点的平衡因子变成 -2,父亲的左孩子平衡因子为1,subLR->bf == -1
parent->bf == -2, subR->bf == 1,subLR->bf == -1
左右双旋之后更新平衡因子变成了 parent->bf == 1, subL->bf == 0,subLR->bf ==0
删除的完整代码
//删除
//更新平衡因子
void updateBFactor(Node* parent, Node* cur)
{while (parent){if (cur == parent->_right){parent->_bf--;}else{parent->_bf++;}if (parent->_bf == 1 || parent->_bf == -1){break;}else if (parent->_bf == 0){cur = parent;parent = parent->_parent;}else if (parent->_bf == 2 || parent->_bf == -2){//parent 所在的子树出现不平衡,需要旋转处理if (parent->_bf == 2){if (parent->_right->_bf == 0){//旋转之前,先保存parent的右边孩子Node* subR = parent->_right;RotateL(parent);parent->_bf = 1; //左孩子subR->_bf = -1; //变成了新根//parent->_parent->_bf = -1; //直接用这和上一句等价的}else if (parent->_right->_bf == 1){RotateL(parent);}else{RotateRL(parent);}}else if (parent->_bf == -2){if (parent->_left->_bf == 0){Node* subL = parent->_left;RotateR(parent);parent->_bf = -1;subL->_bf = 1;}else if (parent->_left->_bf == -1){RotateR(parent);}else{RotateLR(parent);}}break;}}
}bool Erase(const pair<K, V>& kv)
{Node* parent = nullptr;Node* cur = _root;while (cur){if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else{//找到了,先更新平衡因子,如果先删除会导致一些问题,比如cur已经不在了再去访问就会崩// 就算你没有释放cur,但是已经修改了链接关系,还是会导致树的高度有变化// 1、左为空// 2、右为空// 3、左右都不为空if (cur->_left == nullptr){//改变指向之前先更新平衡因子updateBFactor(parent, cur);if (cur == _root){_root = cur->_right;if (_root) //更新parent指向为nullptr_root->_parent = nullptr;}else{if (parent->_right == cur)parent->_right = cur->_right;elseparent->_left = cur->_right;if (cur->_right) //更新parentcur->_right->_parent = parent;}delete cur;}else if (cur->_right == nullptr){//改变指向之前先更新平衡因子updateBFactor(parent, cur);if (cur == _root){_root = cur->_left;if (_root)_root->_parent = nullptr;}else{if (parent->_left == cur)parent->_left = cur->_left;elseparent->_right = cur->_left;if (cur->_left)cur->_left->_parent = parent;}delete cur;}else{Node* rightMinParent = cur;Node* rightMin = cur->_right;while (rightMin->_left){rightMinParent = rightMin;rightMin = rightMin->_left;}//替代cur->_kv = rightMin->_kv;//更新平衡因子updateBFactor(rightMinParent, rightMin);//转换成删除rightMin (rightMin是左为空,父亲指向它的右边)if (rightMin == rightMinParent->_left)rightMinParent->_left = rightMin->_right;elserightMinParent->_right = rightMin->_right;if (rightMin->_right) //链接parentrightMin->_right->_parent = rightMinParent;delete rightMin;}return true;}}return false;
}
AVL树插入与删除的完整代码
AVLTree.h
#pragma once
template<class K, class V>
struct AVLTreeNode
{AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;int _bf; //balance factor 平衡因子pair<K, V> _kv;AVLTreeNode(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){//1,先按搜索树的规则进行插入if (_root == nullptr){_root = new Node(kv);return true;}Node* parent = nullptr;Node* cur = _root;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 (parent->_kv.first < kv.first){parent->_right = cur;cur->_parent = parent;}else{parent->_left = cur;cur->_parent = parent;}//2,更新平衡因子while (parent){if (cur == parent->_right){parent->_bf++;}else{parent->_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){//parent 所在的子树出现不平衡,需要旋转处理if (parent->_bf == 2){if (cur->_bf == 1){RotateL(parent);}else if (cur->_bf == -1) //双旋{RotateRL(parent);}}else if (parent->_bf == -2){if (cur->_bf == -1){RotateR(parent);}else if (cur->_bf == 1) //双旋{RotateLR(parent);}}break;}}return true;}//删除//更新平衡因子void updateBFactor(Node* parent, Node* cur){while (parent){if (cur == parent->_right){parent->_bf--;}else{parent->_bf++;}if (parent->_bf == 1 || parent->_bf == -1){break;}else if (parent->_bf == 0){cur = parent;parent = parent->_parent;}else if (parent->_bf == 2 || parent->_bf == -2){//parent 所在的子树出现不平衡,需要旋转处理if (parent->_bf == 2){if (parent->_right->_bf == 0){//旋转之前,先保存parent的右边孩子Node* subR = parent->_right;RotateL(parent);parent->_bf = 1; //左孩子subR->_bf = -1; //变成了新根//parent->_parent->_bf = -1; //直接用这和上一句等价的}else if (parent->_right->_bf == 1){RotateL(parent);}else{RotateRL(parent);}}else if (parent->_bf == -2){if (parent->_left->_bf == 0){Node* subL = parent->_left;RotateR(parent);parent->_bf = -1;subL->_bf = 1;}else if (parent->_left->_bf == -1){RotateR(parent);}else{RotateLR(parent);}}break;}}}bool Erase(const pair<K, V>& kv){Node* parent = nullptr;Node* cur = _root;while (cur){if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else{//找到了,先更新平衡因子,如果先删除会导致一些问题,比如cur已经不在了再去访问就会崩// 就算你没有释放cur,但是已经修改了链接关系,还是会导致树的高度有变化// 1、左为空// 2、右为空// 3、左右都不为空if (cur->_left == nullptr){//改变指向之前先更新平衡因子updateBFactor(parent, cur);if (cur == _root){_root = cur->_right;if (_root) //更新parent指向为nullptr_root->_parent = nullptr;}else{if (parent->_right == cur)parent->_right = cur->_right;elseparent->_left = cur->_right;if (cur->_right) //更新parentcur->_right->_parent = parent;}delete cur;}else if (cur->_right == nullptr){//改变指向之前先更新平衡因子updateBFactor(parent, cur);if (cur == _root){_root = cur->_left;if (_root)_root->_parent = nullptr;}else{if (parent->_left == cur)parent->_left = cur->_left;elseparent->_right = cur->_left;if (cur->_left)cur->_left->_parent = parent;}delete cur;}else{Node* rightMinParent = cur;Node* rightMin = cur->_right;while (rightMin->_left){rightMinParent = rightMin;rightMin = rightMin->_left;}//替代cur->_kv = rightMin->_kv;//更新平衡因子updateBFactor(rightMinParent, rightMin);//转换成删除rightMin (rightMin是左为空,父亲指向它的右边)if (rightMin == rightMinParent->_left)rightMinParent->_left = rightMin->_right;elserightMinParent->_right = rightMin->_right;if (rightMin->_right) //链接parentrightMin->_right->_parent = rightMinParent;delete rightMin;}return true;}}return false;}//左单旋void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;subR->_left = parent;Node* ppNode = parent->_parent;parent->_parent = subR;//1,parent是原来这棵树的根,现在subR是根//2,parent 为根的树只是整颗树中的子树,改变链接关系,那么subR就要顶替它的位置if (_root == parent){_root = subR;subR->_parent = nullptr;}else{if (ppNode->_left == parent)ppNode->_left = subR;elseppNode->_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;subL->_right = parent;Node* ppNode = parent->_parent;parent->_parent = subL;if (_root == parent){_root = subL;subL->_parent = nullptr;}else{if (ppNode->_left == parent)ppNode->_left = subL;elseppNode->_right = subL;subL->_parent = ppNode;}subL->_bf = parent->_bf = 0;}//右左双旋void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(parent->_right);RotateL(parent);if (bf == -1){parent->_bf = 0;subR->_bf = 1;subRL->_bf = 0;}else if (bf == 1){subR->_bf = 0;parent->_bf = -1;subRL->_bf = 0;}else if (bf == 0){subR->_bf = 0;parent->_bf = 0;subRL->_bf = 0;}}//左右双旋void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(subL);RotateR(parent);if (bf == 1){parent->_bf = 0;subL->_bf = -1;subLR->_bf = 0;}else if (bf == -1){parent->_bf = 1;subL->_bf = 0;subLR->_bf = 0;}else if (bf == 0){parent->_bf = 0;subL->_bf = 0;subLR->_bf = 0;}}void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_InOrder(root->_right);}void InOrder(){_InOrder(_root);}//判断是否平衡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;}bool _IsBalanceTree(Node* root){//空树也是AVL树if (root == nullptr)return true;//计算节点的平衡因子:即左右子树的高度差int leftHeight = height(root->_left);int rightHeight = height(root->_right);int diff = rightHeight - leftHeight;//如果计算出的平衡因子与该节点不相等,// 或者该节点的平衡因子的绝对值超过1,则一定不是AVL树if (abs(diff) >= 2){cout << root->_kv.first << "高度异常" << endl;return false;}if (root->_bf != diff){cout << root->_kv.first << "平衡因子异常" << endl;return false;}//该节点的左和右如果都是AVL树,则该树一定是AVl树return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);}bool IsBalance(){return _IsBalanceTree(_root);}
private:Node* _root = nullptr;
};void TestAVLTree()
{//int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 }; int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };//int a[] = { 5, 30, 70, 11, 2, 1, 18, 100, 10000 };AVLTree<int, int> t;for (auto e : a){t.Insert(make_pair(e, e));}t.InOrder();cout << t.IsBalance() << endl;cout << endl;//删除节点测试for (auto& e : a){t.Erase(make_pair(e, e));cout << "Erase" << e << "->";cout << t.IsBalance() << endl;}t.InOrder();cout << t.IsBalance() << endl;
}
test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include "AVLTree.h"int main()
{TestAVLTree();return 0;
}
AVL树的性能
-
特点:
- 绝对平衡:AVL树是一种严格平衡的二叉搜索树,每个节点的左右子树高度差的绝对值不超过1。
- 高效查询:由于树的高度始终保持在O(log N),查询操作的时间复杂度为O(log N),非常高效。
-
局限性:
- 插入和删除操作复杂:在插入和删除节点时,为了保持树的平衡,可能需要进行多次旋转操作,这可能导致性能下降。
- 删除时的潜在问题:删除操作有时可能需要从删除点一直旋转到根节点,以维持整个树的平衡,这增加了操作的复杂性和时间开销。
-
适用场景:
- 静态数据集:如果数据集的大小是固定的(即数据不会频繁增删),并且需要高效的查询和有序性,AVL树是一个很好的选择。
- 动态数据集:对于频繁进行插入和删除操作的数据集,AVL树可能不是最佳选择,因为维护平衡所需的额外操作会显著影响性能。