【C++】AVL树
一、AVL树的概念
二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查
找元素相当于在顺序表中搜索元素,效率低下。从原来的时间复杂度O(logn)转变为O(n)
因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
- 它的左右子树都是AVL树
- 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/1/0)
二、AVL树节点的定义
AVL的概念:左右子树高度差不超过1
因此要在二叉搜索树上增加一个平衡因子(来记录左右子树的高度差),当平衡因子超过1时,就要更新向上调整父节点的平衡因子(因为子节点的平衡因子发生变化,间接的导致父节点的平衡因子发生改变),这时就要知道父节点的位置,因此在二叉搜索树上要多维护一个父节点的指针。
struct AVLTreeNode
{pair<K, V> _key;AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;int _bf; // balance fatcorAVLTreeNode(const pair<K, V>& key): _key(key), _left(nullptr), _right(nullptr), _parent(nullptr), _bf(0){}
};
三、AVL树的插入
AVL树就是在二叉搜索树的基础上引入了平衡因子
1. 按照二叉搜索树的方式插入新节点
2. 调整节点的平衡因子
下面是二叉搜索树的插入,这个树的结构是左子树小于根,右子树大于根。
bool Insert(const pair<K, V>& key){if (_root == nullptr){_root = new Node(key);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_key.first < key.first){parent = cur;cur = cur->_right;}else if (cur->_key.first > key.first){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(key);if (parent->_key.first < key.first){parent->_right = cur;}else {parent->_left = cur;}
}
调整节点的平衡因子
插入新节点,会影响部分祖先节点的平衡因子,因此要不断的更新平衡因子。
插入在左子树,parent平衡因子--
插入在右子树,parent平衡因子++
1、parent的平衡因子 == 0
parent的平衡因子更新前是 1 or -1,插入节点插入在矮的那边。parent所在子树的高度不变,不需要继续往上更新
2、parent的平衡因子 == 1 / -1
说明parent的平衡因子更新前是0,插入节点插入在任意一边。parent所在的子树高度都会变化了,需要继续往上更新
3、parent的平衡因子 == 2 / -2
说明parent的平衡因子更新前是1 / -1,插入节点插入在高的那边,进一步加剧了parent所在子树的不平衡,所以要发生旋转,旋转后要更新平衡因子
// 因为因为父节点要连接一下父节点的位置cur->_parent = parent;// 更新平衡因子while (parent){if (cur == parent->_left)parent->_bf--;elseparent->_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){//旋转处理}else {assert(false);}}
3.1 AVL左单旋
上面图中的h可以认为是抽象图,就是二叉树搜索树的分支。
上面的图可以认为在subR的右子树(记住是右子树)中任意插入一个节点,使右子树的高度+1,导致subR的平衡因子发生改变,从而影响parent的平衡因子变成了2,从而发生旋转。发生左旋转的条件是什么?是parent的平衡因子为2,subR的平衡因子为1时发生左旋转。
代码实现一下
void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;Node* parentParent = parent->_parent;subR->_left = parent;parent->_parent = subR;if (parentParent == nullptr){_root = subR;subR->_parent = nullptr;}else {if (parentParent->_left == parent){parentParent->_left = subR;}else {parentParent->_right = subR;}subR->_parent = parentParent;}parent->_bf = subR->_bf = 0;}
3.2 AVL右单旋
subL为parent的左子树,subLR为subL的右子树,当a这个位置高度变为h+1时,parent平衡因子为-2,subL平衡因子为-1时,此时发生右旋转,parent的左子树变为subLR,subL变为父节点且右子树为parent
代码实现一下
void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* parentParent = parent->_parent;subL->_right = parent;parent->_parent = subL;if (parentParent == nullptr){_root = subL;subL->_parent = nullptr;}else{if (parent == parentParent->_left){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent;}parent->_bf = subL->_bf = 0;}
3. 2 左右双旋
左旋
如果subRL这个二叉树中高度增加1时也就是说当parent的平衡因子为2,subR的平衡因子为-1时左单旋,我们不难发现旋转的结果与预期不符
右旋如果subLR这个二叉树高度增加1时也就是说parent的平衡因子为-2,subL的平衡因子为1时进行右单旋,旋转的结果与预期不符。旋来旋去发现高度没有变化。
如何解决parent的平衡因子为2,subR的平衡因子为-1的问题?
上图就是解决方式,增加节点后的第二个图,90为parent进行右旋到第三张图,之后30再变成parent进行左旋。
这里最麻烦的是平衡因子的维护
当h == 0时
也就是说subRL这个节点为新增节点,subRL与parent与subR平衡因子都为0。
当h > 0 时
因为增加节点有可能在b或者在c,不能确定parent这个位置平衡因子是-1/0还是subR这个位置的平衡因子是0/1,如何解决呢?
我们记录一下插入节点后subRL的平衡因子,如果subRL的平衡因子为1,经过左右双旋后,parent平衡因子为-1,subR为0,subRL为0。如果subRL的平衡因子为-1,经过左右双旋后,parent的平衡因子为0,subR为1,subRL为0。
代码实现
void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(parent->_right);RotateL(parent);if (bf == 0){subRL->_bf = 0;subR->_bf = 0;parent->_bf = 0;}else if (bf == 1){subR->_bf = 0;subRL->_bf = 0;parent->_bf = -1;}else if (bf == -1){subR->_bf = 1;subRL->_bf = 0;parent->_bf = 0;}else{assert(false);}}
如何解决parent的平衡因子为-2,subR的平衡因子为1的问题?
上图就是解决方式,增加节点后的第二个图,30为parent进行左旋到第三张图,之后90再变成parent进行右旋。
平衡因子的维护
这里也要分三种情况来进行判断
当h == 0时
subRL为新插入节点时,parent的平衡因子与subL和subLR都为0
当h > 0时
新增节点插入b位置时,subLR的平衡因子为-1时,双旋后parent的平衡因子为1,subL和subLR的平衡因子为0。
新增节点插入c位置时,subLR的平衡因子为1时,双旋后parent的平衡因子为0,subL的平衡因子为-1,subLR的平衡因子为0。
代码实现
void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent);if (bf == 0){parent->_bf = 0;subL->_bf = 0;subLR->_bf = 0;}else if (bf == -1){parent->_bf = 1;subL->_bf = 0;subLR->_bf = 0;}else if (bf == 1){parent->_bf = 0;subL->_bf = -1;subLR->_bf = 0;}else {assert(false);}}