VAL树的特性
- 左右子树高度差的绝对值不超过1。(即左右子树高度差取值为-1,0,1)
- 且左右子树均为VAL树
- 右子树的值大于左子树的值
在搜索二叉树中我们提及了搜索二叉树的退化问题。
当有序(升序或降序)地插入时,搜索二叉树就会出现下图的情况,搜索次数从树的高度次退化成n次。而平衡搜索二叉树就解决了这一问题。
平衡方法
在解决方案中,左右子树的差值被定为平衡因子,所以上述例子用平衡二叉搜索树的角度来看如下图。此时出现了不被允许的2和-2,意味着平衡被打破,需要进行调整平衡。
插入时平衡因子的变化逻辑
以下绿色方框代表一棵子树,h为其高度,且h>=0,具体节点上方的数字为平衡因子。
因为插入节点,导致60这个节点的左右子树失衡了。
如图我们可以看到,根据插入位置的不同,其父节点的平衡因子的变化也不同。因为此处我使用的平衡因子 = 右子树高度 - 左子树高度。所以当插入到右子树时,父节点平衡因子+1,插入到左子树时,父节点平衡因子-1。
了解完插入时平衡因子的变化,我们再来了解一下变化的两种情况。
(1)插入后父节点平衡因子变为1或-1的,那么其原来的平衡因子是0,意味着该父节点原本平衡,插入节点后打破了平衡使该父节点为根的整棵子树高度产生变化,此时平衡因子需要继续向上更新,于是60这个节点也受其影响改变了,此时出现了不被允许的失衡,此时就要旋转调整平衡。
(2)第二种情况比较简单,插入后父节点平衡因子变为0,意味着其原本有一些失衡,但还在允许范围内,但插入后完全平衡了,此时无需再继续更新,因为高度没有改变。
总结:亚健康(-1、1)到健康(0)不用管,反之健康到亚健康要去向上更新平衡因子,因为其父节点可能直接就生病了。
// 在AVL树中插入值为data的节点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->_pRight;}else if (data < cur->_data){parent = cur;cur = cur->_pLeft;}else{return false;}}cur = new Node(data);if (parent->_data < data){parent->_pRight = cur;}else{parent->_pLeft = cur;}cur->_pParent = parent;//调整平衡因子while (parent){if (cur == parent->_pLeft){parent->_bf--;}else{parent->_bf++;}//插入后恰好平衡了,跳出循环if (parent->_bf == 0){break;} //高度出现变化但还未失衡,向上走进行判断else if (parent->_bf == 1 || parent->_bf == -1){cur = parent;parent = parent->_pParent;} //已经失衡需要进行调整else if (parent->_bf == 2 || parent->_bf == -2){if (parent->_bf == -2 && cur->_bf == -1){//右单旋RotateR(parent);}else if (parent->_bf == 2 && cur->_bf == 1){//左单旋RotateL(parent);}else if (parent->_bf == 2 && cur->_bf == -1){//右左双旋RotateRL(parent);}else if (parent->_bf == -2 && cur->_bf == 1){//左右双旋RotateLR(parent);}break;}else{//理论而言不可能出现这个情况(此时小于-2或大于2)assert(false);}}return true;}
旋转调整
有了前面插入时平衡因子变化逻辑的铺垫,失衡时的情况就简化成了:
失衡节点无非是-2或2,造就失衡节点的子节点无非是-1,1(产生高度变化)。排列组合后就出现了四种情况。于是出现四种旋转方式与其一一对应。
右单旋
此时的失衡情况为:左左(插入在失衡节点的左节点的左子树中),此时失衡节点平衡因子为-2,产生高度变化的子节点平衡因子为-1。
所以右单旋适用于“左左”,即parent的平衡因子为-2,高度变化的子节点的平衡因子为-1。
右单旋过程:
可以看到,调整后30和60这两个节点的平衡因子都为0了。
那么为什么要这么做?
现在我们先来分析一下他们的大小关系。
比较大小后发现,60非常适合当作30的右子树根节点,同时又做b子树的父节点,因为60>b子树任意值>30。
所以右单旋就是将失衡节点及其右子树下压,当作高度出现变化的节点的右子树,再连接上b子树和60这个节点,就使30的左右子树高度一致了。左子树高度是h+1(1为插入的节点),右边也是h+1(1为原父节点),此时30成为新父节点。
// 右单旋void RotateR(Node* pParent){Node* subL = pParent->_pLeft;Node* subLR = subL->_pRight;pParent->_pLeft = subLR;if (subLR)subLR->_pParent = pParent;subL->_pRight = pParent;Node* temp = pParent->_pParent;pParent->_pParent = subL;//pParent有可能为根,右单旋后应该更新根节点指向。if (pParent == _pRoot){_pRoot = subL;_pRoot->_pParent = nullptr;}else{if (temp->_pLeft == pParent){temp->_pLeft = subL;}else{temp->_pRight = subL;}subL->_pParent = temp;}pParent->_bf = subL->_bf = 0;}
左单旋
与右单旋同理,不过多赘述。
左单旋适用于“右右”,即parent的平衡因子为2,高度变化的子节点的平衡因子为1。
旋转过程:
// 左单旋void RotateL(Node* pParent){Node* subR = pParent->_pRight;Node* subRL = subR->_pLeft;pParent->_pRight = subRL;if (subRL)subRL->_pParent = pParent;subR->_pLeft = pParent;Node* temp = pParent->_pParent;pParent->_pParent = subR;//pParent有可能为根,右单旋后应该更新根节点指向。if (pParent == _pRoot){_pRoot = subR;_pRoot->_pParent = nullptr;}else{if (temp->_pLeft == pParent){temp->_pLeft = subR;}else{temp->_pRight = subR;}subR->_pParent = temp;}pParent->_bf = subR->_bf = 0;}
左右双旋
如图所示,当出现插入节点在失衡节点(90)其左孩子(30)的右孩子(60)下的子树时,就需要用到左右双旋,单旋是无法解决的。
即:失衡节点平衡因子为-2,且其左孩子的平衡因子为1时使用左右双旋,以下简称“左右”。
第一步,我们对30为根的子树进行左单旋。(第一次旋转后,失衡的状况从“左右”变成了可以使用右单旋的“左左”)
第二步,我们对90为根的子树进行右单旋。
代码实际上是对左单旋和右单旋的复用,然后再处理节点间的连接关系。
// 左右双旋void RotateLR(Node* pParent){Node* subL = pParent->_pLeft;Node* subLR = subL->_pRight;int bf = subLR->_bf;RotateL(subL);RotateR(pParent);subLR->_bf = 0;if (bf == 1){pParent->_bf = 0;subL->_bf = -1;}else if (bf == -1){pParent->_bf = 1;subL->_bf = 0;}else{pParent->_bf = 0;subL->_bf = 0;}}
右左双旋
失衡节点平衡因子为2,且其左孩子的平衡因子为-1时使用左右双旋。
第一步,我们对90为根的子树进行右单旋。(第一次旋转后,失衡的状况从“右左”变成了可以使用左单旋的“右右”)
第二步,我们对30为根的子树进行左单旋。
// 右左双旋void RotateRL(Node* pParent){Node* subR = pParent->_pRight;Node* subRL = subR->_pLeft;int bf = subRL->_bf;RotateR(subR);RotateL(pParent);subRL->_bf = 0;if (bf == 1){pParent->_bf = -1;subR->_bf = 0;}else if (bf == -1){pParent->_bf = 0;subR->_bf = 1;}else{pParent->_bf = 0;subR->_bf = 0;}}
完整代码实现
#pragma once
#include<assert.h>
#include<vector>
#include<iostream>
using namespace std;namespace VAL
{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){}Node* Find(const T& data){Node* cur = _pRoot;while (cur){if (cur->_data < data){cur = cur->_pRight;}else if (cur->_data > data){cur = cur->_pLeft;}else{return cur;}}return nullptr;}// 在AVL树中插入值为data的节点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->_pRight;}else if (data < cur->_data){parent = cur;cur = cur->_pLeft;}else{return false;}}cur = new Node(data);if (parent->_data < data){parent->_pRight = cur;}else{parent->_pLeft = cur;}cur->_pParent = parent;//调整平衡因子while (parent){if (cur == parent->_pLeft){parent->_bf--;}else{parent->_bf++;}//插入后恰好平衡了,跳出循环if (parent->_bf == 0){break;} //高度出现变化但还未失衡,向上走进行判断else if (parent->_bf == 1 || parent->_bf == -1){cur = parent;parent = parent->_pParent;} //已经失衡需要进行调整else if (parent->_bf == 2 || parent->_bf == -2){if (parent->_bf == -2 && cur->_bf == -1){RotateR(parent);}else if (parent->_bf == 2 && cur->_bf == 1){RotateL(parent);}else if (parent->_bf == 2 && cur->_bf == -1){RotateRL(parent);}else if (parent->_bf == -2 && cur->_bf == 1){RotateLR(parent);}break;}else{// 理论而言不可能出现这个情况assert(false);}}return true;}// AVL树的验证bool IsAVLTree(){return _IsAVLTree(_pRoot);}int Size(){return _Size(_pRoot);}void InOrder(){_InOrder(_pRoot);cout << endl;}size_t Height(){return _Height(_pRoot);}private:void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_pLeft);cout << root->_data << endl;_InOrder(root->_pRight);}bool _IsAVLTree(Node* root){if (root == nullptr)return true;int leftHeight = _Height(root->_pLeft);int rightHeight = _Height(root->_pRight);// 不平衡if (abs(leftHeight - rightHeight) >= 2){cout << root->_data << endl;return false;}// 顺便检查一下平衡因子是否正确if (rightHeight - leftHeight != root->_bf){cout << root->_data << endl;return false;}return _IsAVLTree(root->_pLeft)&& _IsAVLTree(root->_pRight);}size_t _Height(Node* _pRoot){if (_pRoot == nullptr) return 0;return max(_Height(_pRoot->_pLeft), _Height(_pRoot->_pRight)) + 1;}int _Size(Node* root){return root == nullptr ? 0 : _Size(root->_pLeft) + _Size(root->_pRight) + 1;}// 右单旋void RotateR(Node* pParent){Node* subL = pParent->_pLeft;Node* subLR = subL->_pRight;pParent->_pLeft = subLR;if (subLR)subLR->_pParent = pParent;subL->_pRight = pParent;Node* temp = pParent->_pParent;pParent->_pParent = subL;//pParent有可能为根,右单旋后应该更新根节点指向。if (pParent == _pRoot){_pRoot = subL;_pRoot->_pParent = nullptr;}else{if (temp->_pLeft == pParent){temp->_pLeft = subL;}else{temp->_pRight = subL;}subL->_pParent = temp;}pParent->_bf = subL->_bf = 0;}// 左单旋void RotateL(Node* pParent){Node* subR = pParent->_pRight;Node* subRL = subR->_pLeft;pParent->_pRight = subRL;if (subRL)subRL->_pParent = pParent;subR->_pLeft = pParent;Node* temp = pParent->_pParent;pParent->_pParent = subR;//pParent有可能为根,右单旋后应该更新根节点指向。if (pParent == _pRoot){_pRoot = subR;_pRoot->_pParent = nullptr;}else{if (temp->_pLeft == pParent){temp->_pLeft = subR;}else{temp->_pRight = subR;}subR->_pParent = temp;}pParent->_bf = subR->_bf = 0;}// 右左双旋void RotateRL(Node* pParent){Node* subR = pParent->_pRight;Node* subRL = subR->_pLeft;int bf = subRL->_bf;RotateR(subR);RotateL(pParent);subRL->_bf = 0;if (bf == 1){pParent->_bf = -1;subR->_bf = 0;}else if (bf == -1){pParent->_bf = 0;subR->_bf = 1;}else{pParent->_bf = 0;subR->_bf = 0;}}// 左右双旋void RotateLR(Node* pParent){Node* subL = pParent->_pLeft;Node* subLR = subL->_pRight;int bf = subLR->_bf;RotateL(subL);RotateR(pParent);subLR->_bf = 0;if (bf == 1){pParent->_bf = 0;subL->_bf = -1;}else if (bf == -1){pParent->_bf = 1;subL->_bf = 0;}else{pParent->_bf = 0;subL->_bf = 0;}}private:Node* _pRoot;};
}