引言:
AVL树是一种特殊的二叉搜索树,二叉搜索树虽然可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
一棵AVL树具有如下性质:
- 左右子树都是AVL树
- 左右子树高度差绝对值不超过1
一、AVL树结点的定义
template<class T>
struct AVLTreeNode
{
AVLTreeNode(const T& data): _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树的插入过程可以分为两步:
- 按照二叉搜索树的方式插入新节点
- 调整节点的平衡因子
bool Insert(const T& data){//如果是空树,直接插入并结束if (_pRoot == nullptr){_pRoot = new Node(data);return true;}//这一块代码的目的是找到插入位置的父亲结点Node* parent = nullptr;Node* cur = _pRoot;while (cur){if (data < cur->_data){parent = cur;cur = cur->_pLeft;}else if (data > cur->_data){parent = cur;cur = cur->_pRight;}else{return false;}}if (cur == parent->_pLeft){cur = new Node(data);parent->_pLeft = cur;}else{cur = new Node(data);parent->_pRight = cur;}//从parent结点开始向上更新祖先的平衡因子while (parent){if (parent->_bf == 0){break;//插入之后,该结点平衡因子为0说明以该结点为根的子树平衡了//且插入前后高度不变,所以这个结点的祖先的平衡因子不会受到影响,结束}else if (parent->_bf == 1 || parent->_bf == -1){//平衡因子从0变为1或者-1,祖先可能变成2或-2,需要继续向上更新cur = parent;parent = parent->_pParent;}else{//结点的平衡因子变成了2或者-2,需要进行旋转了//这里开始需要对AVL树进行旋转,之后再讲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{RotateLR(parent);}break;}}return true;}
三、AVL树的旋转
1.右单旋
这是一幅抽象图,左子树较高且在左子树的左侧插入 ,`h>=0,此时需要进行右单旋
我们可以看到这样旋转之后该子树依然是一个二叉搜索树,巧妙的是,根节点的平衡因子被更新 成0了,这说明:在旋转之后不用再对祖先更改平衡因子了
void RotateR(Node* pParent){Node*subL = pParent->_pLeft;Node* subLR = subL->_pRight;pParent->_pLeft = subRL;if (subLR){subLR->_pParent = pParent;}Node*parentParent = pParent->_pParent;subL->_pRight = pParent;if (pRoot == pParent){pRoot = subL;subL->_pParent = nullptr;}else{if (parentParent->_pLeft == pParent){parentParent->_pLeft = subL;}else{parentParent->_pRight = subL;}subL->_pParent = parentParent;}pParent->_bf = subL->_bf = 0;}
2.左单旋
左单旋和右单旋类似,只是方向相反,右子树较高且在右子树的右侧插入
void RotateL(Node* pParent){Node*subR = pParent->_pRight;Node* subRL = subR->_pLeft;pParent->_pRight = subRL;if (subRL){subRL->_pParent = pParent;}Node* parentParent = pParent->_pParent;pParent->_pParent = subR;subR->_pLeft = pParent;if (parent == pRoot){pRoot = subR;subR->_pParent = nullptr;}else{if (parentParent->_pLeft = pParent){parentParent->_pLeft = subR;}else{parentParent->_pRight = subR;}}pParent->_bf = subR->_bf = 0;}
3. 左右双旋
对于单侧插入,只需要旋转一次。但是对于如下的情况,单次旋转无法解决问题
和单旋不同,我们需要对子树再往下画一层,如图所示
这棵树的左子树较高,并且在左子树的右子树插入,导致单旋无法一次解决
旋转过程
step1:
step2:
新插入结点无论插入在subLR的左还是右, 最终subLR平衡因子为0
// 左右双旋void RotateLR(Node* pParent){Node* subL = pParent->_pLeft;Node* subLR = pParent->_pRight;int bf = subLR->_bf;RotateL(subL);RotateR(pParent);//subLR就是新插入结点if (bf == 0){pParent->_bf = subL->_bf = subLR->_bf = 0;}else if (bf == 1){pParent->_bf = -1;subL->_bf = -1;subLR->_bf = 0;}else if(bf==-1){pParent->_bf = 1;subL->_bf = 1;subLR = 0;}}
4.右左双旋
与左右双旋类似
//右左双旋
void RotateRL(Node* pParent){Node* subR = pParent->_pRight;Node* subRL = subR->_pLeft;int bf = subRL->_bf;RotateR(subR);RotateL(pParent);//subRL就是新插入结点if (bf == 0){pParent->_bf = subR->_bf = subRL->_bf = 0;}else if (bf == 1){pParent->_bf = 0;subR->_bf = -1;subRL->_bf = 0;}else if(bf==-1){pParent->_bf = 1;subR->_bf = 1;subRL->_bf = 0;}}