C++AVL树
目录
一.AVL树的性质
二.AVL树节点的定义
三.AVL树插入操作
四.AVL树的旋转操作
4.1左单旋:新节点插入较高右子树的右侧
4.2右单旋:新节点插入较高左子树的左侧
4.3左右双旋:新节点插入较高左子树的右侧
4.4右左双旋 :新节点插入较高右子树的左侧
五.AVL树验证
六.AVL树的删除
七.AVL树性能
一.AVL树的性质
当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整)
特点
- AVL树可以是空树
- 左右子树都是AVL树
- 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
注意:如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在 O(log_2 n),搜索时间复杂度O(log_2 n)。
二.AVL树节点的定义
AVL树的左右子树的高度差不能超过1,那么我们如何让快速判断是不是AVL树呢?
我们可以引入平衡因子来解决这个问题。如果右子树比左子树高一层,则此时平衡因子为1;如果左子树比有子树高一层,则此时平衡因子为-1;如果右子树和左子树一样高,则此时平衡因子为0。(这里平衡因子计算是右子树-左子树,当然左子树-右子树也可以)
依据此规则,如果平衡因子为2/-2,则说明平衡被打破。
在调整不平衡的AVL树时,需要频繁访问父亲节点,所以在AVL树节点定义中,除了定义指向左右节点的指针以外,还需要一个指向父亲节点的指针。
AVL树节点定义如下:
template<class T> struct AVLTreeNode {AVLTreeNode(const T& data = T()): _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr), _data(data), _bf(0){}AVLTreeNode<T>* _pLeft;AVLTreeNode<T>* _pRight;AVLTreeNode<T>* _pParent;T _data;int _bf;// 节点的平衡因子 };
三.AVL树插入操作
AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么 AVL树的插入过程可以分为两步:
- 按照二叉搜索树的方式插入新节点
- 调整节点的平衡因子
平衡因子调节:(pCur为新插入节点,pParent为pCur父亲节点)
pCur插入后,pParent的平衡因子一定需要调整,在插入之前,pParent 的平衡因子分为三种情况:-1,0, 1, 分以下两种情况:
- 如果pCur插入到pParent的左侧,只需给pParent的平衡因子-1即可
- 如果pCur插入到pParent的右侧,只需给pParent的平衡因子+1即可
此时:pParent的平衡因子可能有三种情况:0,正负1, 正负2:
- 如果pParent的平衡因子为0,说明插入之前pParent的平衡因子为正负1,插入后被调整 成0,此时满足 AVL树的性质,插入成功
- 如果pParent的平衡因子为正负1,说明插入前pParent的平衡因子一定为0,插入后被更 新成正负1,此时以pParent为根的树的高度增加,需要继续向上更新
- 如果pParent的平衡因子为正负2,则pParent的平衡因子违反平衡树的性质,需要对其进 行旋转处理
至此我们可以对AVL插入操作有了大致框架:
bool Insert(const T& data) {Node* newnode = new Node(data);if (_pRoot == nullptr){_pRoot = newnode;return true;}Node* pcur = _pRoot;Node* parent = nullptr;while (pcur){parent = pcur;if (data < pcur->_data){pcur = pcur->_pLeft;}else if (data > pcur->_data){pcur = pcur->_pRight;}else{return false;}}if (parent->_data < data){parent->_pRight = newnode;}elseparent->_pLeft = newnode;//从下往上更新平衡因子pcur = newnode;while (parent){if (pcur == parent->_pLeft){parent->_bf--;}else{parent->_bf++;}if (parent->_bf == 0){//...}else if (parent->_bf == 1 || parent->_bf == -1){//...}else if (parent->_bf == 2 || parent->_bf == -2){//...break;//旋转后字数高度不变,不需要向上调整}else{return false;}}return true; }
四.AVL树的旋转操作
在插入操作时我们提出有2和-2的特使情况,这时候就需要进行旋转操作。
4.1左单旋:新节点插入较高右子树的右侧
由图可以看出,在a插入一个节点则右边比较高,为了保持平衡我们需要向左旋转。
代码如下:
// 左单旋
void RotateL(Node* parent)
{Node* subr = parent->_pRight;Node* subrl = subr->_pLeft;parent->_pRight = subrl;if (subrl)subrl->_pParent = parent;Node* pparent = parent->_pParent;parent->_pParent = subr;if (parent==_pRoot){_pRoot = subr;subr->_pParent = nullptr;}else{if (pparent->_pLeft == parent){pparent->_pLeft=subr;}else{pparent->_pRight=subr;}subr->_pParent = pparent;}subr->_bf = parent->_bf = 0;
}
4.2右单旋:新节点插入较高左子树的左侧
和左单旋类似,代码如下:
// 右单旋
void RotateR(Node* parent)
{Node* subl = parent->_pLeft;Node* sublr = subl->_pRight;parent->_pLeft = sublr;subl->_pRight = parent;if (sublr){sublr->_pParent = parent;}Node* pparent = parent->_pParent;parent->_pParent = subl;if (_pRoot == parent){_pRoot = subl;subl->_pParent = nullptr;}else{if (pparent->_pLeft == parent){pparent->_pLeft=subl;}else{pparent->_pRight=subl;}subl->_pParent = pparent;}subl->_bf = parent->_bf = 0;
}
4.3左右双旋:新节点插入较高左子树的右侧
分成两种情况:
这两种情况都属于在较高左子树的右侧插入,处理方式都是相同的,唯一的区别在于最后旋转完成后,更新平衡因子时的值不同。
我们对其中一种情况进行分析:
可以看到这两种情况中,如果在b下面插入新节点,那么旋转过后30和60的平衡因子更新成0,90的平衡因子更新成1;如果在c下面插入新节点,则是60和90的平衡因子更新成0,30的平衡因子更新成-1
而新节点究竟插入到了b下面还是在c下面,我们可以通过插入节点后60的平衡因子来判断。
// 左右双旋
void RotateLR(Node* pParent)
{Node* subl = pParent->_pLeft;Node* sublr = subl->_pRight;int bf = sublr->_bf;RotateL(pParent->_pLeft);RotateR(pParent);if (bf == 0){subl->_bf = sublr->_bf = pParent->_bf = 0;}else if (bf == -1){subl->_bf = sublr->_bf = 0;pParent->_bf = 1;}else if (bf == 1){pParent->_bf =sublr->_bf = 0;subl->_bf = -1;}
}
4.4右左双旋 :新节点插入较高右子树的左侧
// 右左双旋
void RotateRL(Node* pParent)
{Node* subr = pParent->_pRight;Node* subrl = subr->_pLeft;int bf = subrl->_bf;RotateR(pParent->_pRight);RotateL(pParent); if (bf == 0){subr->_bf = subrl->_bf = pParent->_bf = 0;}else if (bf == 1){subrl->_bf =subr->_bf= 0;pParent->_bf = -1;}else if (bf == -1){pParent->_bf = subrl->_bf = 0;subr->_bf = 1;}
}
总结: 假如以pParent为根的子树不平衡,即pParent的平衡因子为2或-2,分以下情况考虑
1. pParent的平衡因子为2,说明pParent的右子树高,设pParent的右子树的根为pSubR
- 当pSubR的平衡因子为1时,执行左单旋
- 当pSubR的平衡因子为-1时,执行右左双旋
2. pParent的平衡因子为-2,说明pParent的左子树高,设pParent的左子树的根为pSubL
- 当pSubL的平衡因子为-1是,执行右单旋
- 当pSubL的平衡因子为1时,执行左右双旋
旋转完成后,原pParent为根的子树个高度降低,已经平衡,不需要再向上更新。
五.AVL树验证
AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:
1. 验证其为二叉搜索树
如果中序遍历可得到一个有序的序列,就说明为二叉搜索树
2. 验证其为平衡树
- 每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)
- 节点的平衡因子是否计算正确
// 根据AVL树的概念验证pRoot是否为有效的AVL树
bool _IsAVLTree(Node* pRoot)
{if (pRoot == nullptr){return true;}int leftheight = _Height(pRoot->_pLeft);int rightheight = _Height(pRoot->_pRight);if (rightheight - leftheight !=pRoot->_bf){cout << "平衡因子异常" << endl;return false;}return abs(rightheight - leftheight) <= 1 && _IsAVLTree(pRoot->_pLeft)&& _IsAVLTree(pRoot->_pRight);
}
size_t _Height(Node* pRoot)
{if (pRoot == nullptr){return 0;}size_t lefth=_Height(pRoot->_pLeft);size_t righth = _Height(pRoot->_pRight);return max(lefth, righth)+1;
}
六.AVL树的删除
因为AVL树也是二叉搜索树,可按照二叉搜索树的方式将节点删除,然后再更新平衡因子,只不 错与删除不同的时,删除节点后的平衡因子更新,最差情况下一直要调整到根节点的位置。
七.AVL树性能
AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这 样可以保证查询时高效的时间复杂度,即log_2 (N)。但是如果要对AVL树做一些结构修改的操 作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时, 有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数 据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。
完整代码:
#pragma once
#include<iostream>
using namespace std;
template<class T>
struct AVLTreeNode
{AVLTreeNode(const T& data = T()): _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr), _data(data), _bf(0){}AVLTreeNode<T>* _pLeft;AVLTreeNode<T>* _pRight;AVLTreeNode<T>* _pParent;T _data;int _bf;// 节点的平衡因子
};// AVL: 二叉搜索树 + 平衡因子的限制
template<class T>
class AVLTree
{typedef AVLTreeNode<T> Node;
public:AVLTree(): _pRoot(nullptr){}// 在AVL树中插入值为data的节点bool Insert(const T& data){Node* newnode = new Node(data);if (_pRoot == nullptr){_pRoot = newnode;return true;}Node* pcur = _pRoot;Node* parent = nullptr;while (pcur){parent = pcur;if (data < pcur->_data){pcur = pcur->_pLeft;}else if (data > pcur->_data){pcur = pcur->_pRight;}else{return false;}}if (parent->_data < data){parent->_pRight = newnode;}elseparent->_pLeft = newnode;//从下往上更新平衡因子pcur = newnode;while (parent){if (pcur == parent->_pLeft){parent->_bf--;}else{parent->_bf++;}if (parent->_bf == 0){break;}else if (parent->_bf == 1 || parent->_bf == -1){pcur = parent;parent = pcur->_pParent;}else if (parent->_bf == 2 || parent->_bf == -2){if (parent->_bf == 2 && pcur->_bf == 1){RotateL(parent);}else if (parent->_bf == -2 && pcur->_bf == -1){RotateR(parent);}else if (parent->_bf == -2 && pcur->_bf == 1){RotateLR(parent);}else if (parent->_bf == 2 && pcur->_bf == -1){RotateRL(parent);}break;//旋转后字数高度不变,不需要向上调整}else{return false;}}return true;}// AVL树的验证bool IsAVLTree(){return _IsAVLTree(_pRoot);}void _Inorder(Node* Root){if (Root == nullptr){return ;}_Inorder(Root->_pLeft);cout << Root->_data << " ";_Inorder(Root->_pRight);}void Inorder(){_Inorder(_pRoot);}
private:// 根据AVL树的概念验证pRoot是否为有效的AVL树bool _IsAVLTree(Node* pRoot){if (pRoot == nullptr){return true;}int leftheight = _Height(pRoot->_pLeft);int rightheight = _Height(pRoot->_pRight);if (rightheight - leftheight !=pRoot->_bf){cout << "平衡因子异常" << endl;return false;}return abs(rightheight - leftheight) <= 1 && _IsAVLTree(pRoot->_pLeft)&& _IsAVLTree(pRoot->_pRight);}int _Height(Node* pRoot){if (pRoot == nullptr){return 0;}int lefth=_Height(pRoot->_pLeft);int righth = _Height(pRoot->_pRight);return max(lefth, righth)+1;}// 右单旋void RotateR(Node* parent){Node* subl = parent->_pLeft;Node* sublr = subl->_pRight;parent->_pLeft = sublr;subl->_pRight = parent;if (sublr){sublr->_pParent = parent;}Node* pparent = parent->_pParent;parent->_pParent = subl;if (_pRoot == parent){_pRoot = subl;subl->_pParent = nullptr;}else{if (pparent->_pLeft == parent){pparent->_pLeft=subl;}else{pparent->_pRight=subl;}subl->_pParent = pparent;}subl->_bf = parent->_bf = 0;}// 左单旋void RotateL(Node* parent){Node* subr = parent->_pRight;Node* subrl = subr->_pLeft;parent->_pRight = subrl;if (subrl)subrl->_pParent = parent;Node* pparent = parent->_pParent;parent->_pParent = subr;if (parent==_pRoot){_pRoot = subr;subr->_pParent = nullptr;}else{if (pparent->_pLeft == parent){pparent->_pLeft=subr;}else{pparent->_pRight=subr;}subr->_pParent = pparent;}subr->_bf = parent->_bf = 0;}// 右左双旋void RotateRL(Node* pParent){Node* subr = pParent->_pRight;Node* subrl = subr->_pLeft;int bf = subrl->_bf;RotateR(pParent->_pRight);RotateL(pParent); if (bf == 0){subr->_bf = subrl->_bf = pParent->_bf = 0;}else if (bf == 1){subrl->_bf =subr->_bf= 0;pParent->_bf = -1;}else if (bf == -1){pParent->_bf = subrl->_bf = 0;subr->_bf = 1;}}// 左右双旋void RotateLR(Node* pParent){Node* subl = pParent->_pLeft;Node* sublr = subl->_pRight;int bf = sublr->_bf;RotateL(pParent->_pLeft);RotateR(pParent);if (bf == 0){subl->_bf = sublr->_bf = pParent->_bf = 0;}else if (bf == -1){subl->_bf = sublr->_bf = 0;pParent->_bf = 1;}else if (bf == 1){pParent->_bf =sublr->_bf = 0;subl->_bf = -1;}}private:Node* _pRoot;
};