目录
1、AVL树的概念
2、AVL树结点的定义
3、AVL树的插入
4、AVL树的旋转
4.1 左单旋
4.2 右单旋
4.3 右左双旋
4.4 左右双旋
5、AVL树的验证
6、AVL树的性能
前面对map/multimap/set/multiset进行了简单的介绍,会大仙,这几个容器有个共同点是:其底层都是按照二叉搜索树来实现的,但是二叉搜索树有其自身的缺陷,假如往树中插入的元素有序或者接近有序,二叉搜索树就会退化成单支树,时间复杂度会退化成O(N),因此map、set等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡树来实现。本章介绍的AVL树和下一章介绍的红黑树,就是平衡树
1、AVL树的概念
当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
为什么不将树调整为左右子树的高度相等呢?因为有些结点数量下(如2和4个结点),做不到左右子树高度相等,所以最好的平衡二叉树就是高度不超过1
一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
上面这棵树的平衡因子是用右子树高度-左子树高度,这不是一定的,也可以反过来,并没有严格规定,这篇文章中的平衡因子都是用右子树高度-左子树高度的
因为AVL树每个结点的左右子树高度差不超过1,所以平衡因子只有在-1、0、1是才是正常的
2、AVL树结点的定义
我们这里实现的是KV模型的AVL树,为了与map对应,两个值是以pair的形式存储。并且每个结点中还要增加一个平衡因子。当插入新结点时,为了检查路径上是否出现异常的平衡因子,还要增加一个指向父亲结点的指针,另外原来还有指向左右孩子的指针,称为三叉链结构
template<class K,class V>
struct AVLTreeNode
{pair<K, V> _kv;// 三叉链AVLTreeNode* _left;AVLTreeNode* _right;AVLTreeNode* _parent;int _bf; // 平衡因子AVLTreeNode(const pair<K,V>& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr),_bf(0){}
};
根结点的_parent置为nullptr
3、AVL树的插入
向AVL树中插入一个新结点时,插入的操作是和二叉搜索树一致的,只是插入一个结点后,需要更新一下这个结点到这棵树的根节点路径上部分结点的平衡因子,若是平衡因子出现异常,则需要进行旋转操作。
插入结点,会影响部分祖先结点的平衡因子。因为这里的平衡因子 = 右子树高度 - 左子树高度
结点插入在左子树,其父亲的平衡因子--
结点插入在右子树,其父亲的平衡因子++
是否需要继续往上更新祖先,要看parent所在子树的高度是否发生了变化,此时有3种情况:
1. 更新后parent的平衡因子变成0
说明没插入时parent的平衡因子是-1/1,一边高一边低,插入后,变成两边一样高,parent所在子树的高度没有发生变化,所以不需要向上更新
2. 更新后parent的平衡因子变成-1/1
说明没插入时parent的平衡因子是0,两边一样高,插入后,变成一边高一边低,parent所在子树的高度变高了,所以需要继续向上更新
3. 更新后parent的平衡因子变成-2/2
说明没插入时parent的平衡因子是-1/1,插入结点插入在高的那一边,进一步加剧了parent所在子树的不平衡,已经违反规定,需要旋转处理
bool Insert(const pair<K, V>& kv)
{if (_root == nullptr){_root = new Node(kv);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(kv);if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}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);}}return true;
}
4、AVL树的旋转
4.1 左单旋
当新结点插入到右子树的右侧,需要进行左单旋 ----- 右右:左单旋
这里面h>=0,表示a、b、c是高度为h的AVL子树
左单旋的标志就是出现异常的结点的平衡因子为2,并且其右子树的平衡因子为1,那么这个时候就进行左单旋。
左单旋步骤:
1. 将subRL变成parent的右子树
2. 将parent变成subR的左子树
3. 建立subR与parent的父亲结点parentParent的关系
4. 更新parent和subR的平衡因子
完成代码时,除了要完成每个结点的链接关系,还需要注意重新链接后结点父亲的变化和平衡因子的变化。其中平衡因子只有30和60这两个结点有变化,因为要改变平衡因子,需要改变左右子树的高度,并且这两个结点的平衡因子都变成了0。注意,subRL是有可能为nullptr的,当h等于0时,所以修改其父亲结点时需要判断一下。
void RotateL(Node* parent) // 左单旋
{Node* subR = parent->_right;Node* subRL = subR->_left;Node* parentParent = parent->_parent;// 1. 将subRL变成parent的右子树parent->_right = subRL;if (subRL)subRL->_parent = parent;// 2. 将parent变成subR的左子树subR->_left = parent;parent->_parent = subR;// 3. 建立subR与parent的父亲结点parentParent的关系if (parentParent == nullptr) // 说明parent就是根结点{_root = subR;subR->_parent = nullptr;}else{if (parent == parentParent->_left)parentParent->_left = subR;elseparentParent->_right = subR;subR->_parent = parentParent;}// 4. 更新parent和subR的平衡因子 parent->_bf = subR->_bf = 0;
}
4.2 右单旋
当新插入的结点在左子树的左侧,需要进行右单旋 ----- 左左:右单旋
右单旋的标志就是出现异常的结点的平衡因子为-2,并且其左子树的平衡因子为-1,那么这个时候就进行左单旋。
右单旋的步骤:
1. 将subLR变成parent的左子树
2. 将parent变成subL的右子树
3. 建立subL与parent的父亲结点parentParent的关系
4. 更新parent和subL的平衡因子
void RotateR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;Node* parentParent = parent->_parent;// 1. 将subLR变成parent的左子树parent->_left = subLR;if (subLR)subLR->_parent = parent;// 2. 将parent变成subL的右子树subL->_right = parent;parent->_parent = subL;// 3. 建立subL与parent的父亲结点parentParent的关系if (parentParent == nullptr) // 说明parent就是根结点{_root = subL;subL->_parent = nullptr;}else{if (parentParent->_left == parent)parentParent->_left = subL;elseparentParent->_right = subL;subL->_parent = parentParent;}// 4. 更新parent和subL的平衡因子 parent->_bf = subL->_bf = 0;
}
4.3 右左双旋
当新插入的结点在右子树的左侧,需要先进行右单旋,再进行左单旋 ----- 右左:右左双旋
右左双旋的标志是出现异常的结点的平衡因子是2,其右子树的平衡因子是-1
第一种情况:在右子树的左子树的右子树插入(即c子树插入)
第二种情况:在右子树的左子树的左子树插入(即b子树插入)
这两种情况都是先进行右单旋,然后进行左单旋,只是旋转完成后,个别结点的平衡因子不同。如何区分是在b插入还是c插入呢?
当h > 0时
1. 插入结点后,若右子树的左子树这个结点的平衡因子是1,则是在c插入的
2. 插入结点后,若右子树的左子树这个结点的平衡因子是-1,则是在b插入的
上面两幅图就是h > 0的两种情况的图
当h == 0 时
3. 插入之前连60这个结点都没有,60这个结点自己就是新增,此时60这个结点的平衡因子是0
右左单旋步骤:
1、计算subRL的平衡因子
2、对subR进行右单旋
3、对parent进行左单旋
4、更新parent、subR、subRL的平衡因子
void RotateRL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;// 1、计算subRL的平衡因子int bf = subRL->_bf;// 2、对subR进行右单旋RotateR(subR);// 3、对parent进行左单旋RotateL(parent);// 4、更新parent、subR、subRL的平衡因子if (bf == 0){subR->_bf = 0;subRL->_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);}
}
4.4 左右双旋
当新插入的结点在左子树的右侧,需要先进行左单旋,再进行右单旋 ----- 左右:左右双旋
左右双旋的标志是出现异常的结点的平衡因子是-2,其左子树的平衡因子是1
第一种情况:在左子树的右子树的左子树插入(即b子树插入)
第二种情况:在左子树的右子树的右子树插入(即c子树插入)
第三种情况:当h == 0时
左右单旋的步骤:
1. 计算subLR的平衡因子
2. 对subL进行左单旋
3. 对parent进行右单旋
4. 更新parent、subR、subRL的平衡因子
void RotateLR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;// 1. 计算subLR的平衡因子int bf = subLR->_bf;// 2. 对subL进行左单旋RotateL(subL);// 3. 对parent进行右单旋RotateR(parent);// 4. 更新parent、subR、subRL的平衡因子if (bf == 0){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 0;}else if (bf == 1){subL->_bf = -1;subLR->_bf = 0;parent->_bf = 0;}else if (bf == -1){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 0;}else{assert(false);}
}
会发现同号单旋,异号双旋
所以此时插入操作的代码为
bool Insert(const pair<K, V>& kv)
{// 若_root为空,直接让新插入的结点变成根if (_root == nullptr){_root = new Node(kv);return true;}// 若_root不为空,按二叉搜索树的规则找到插入的位置,同时也要记录父亲结点Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else{return false;}}// 找到要插入的位置后,根据传过来的值创建一个结点,然后判断其是父亲节点的左边还是右边,链接上去cur = new Node(kv);if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}// 因为现在是三叉链结果,还要链接一下父亲结点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){// 不平衡了,旋转处理if (parent->_bf == 2 && parent->_right->_bf == 1){RotateL(parent);}else if (parent->_bf == -2 && parent->_left->_bf == -1){RotateR(parent);}else if (parent->_bf == 2 && parent->_right->_bf == -1){RotateRL(parent);}else{RotateLR(parent);}break; // 旋转完后,一定平衡了,所以可以跳出循环}else{assert(false);}}return true;
}
注意,旋转完成之后,这颗子树的父亲结点的平衡因子一定是0,所以可以直接跳出循环,不需要继续向上更新平衡因子
5、AVL树的验证
在前面,我们已经实现了插入操作,那么按照这个插入函数获得的真的是一颗AVL树吗?
所以,我们需要进行验证。验证只需要计算每个结点的左右树高,判断左右树高的差值小于2即可
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;// 计算pRoot节点的平衡因子:即pRoot左右子树的高度差int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);int diff = rightHeight - leftHeight;// 如果计算出的平衡因子与pRoot的平衡因子不相等,或者// pRoot平衡因子的绝对值超过1,则一定不是AVL树if (abs(diff) >= 2 || root->_bf != diff)return false;// pRoot的左和右如果都是AVL树,则该树一定是AVL树return _IsBalanceTree(root->_left) && _IsBalanceTree(root -> _right);
}
这两个函数需要写在AVL树内部,因为用到了_root
6、AVL树的性能
AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即log2 (N)。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。
template<class K,class V>
struct AVLTreeNode
{pair<K, V> _kv;// 三叉链AVLTreeNode* _left;AVLTreeNode* _right;AVLTreeNode* _parent;int _bf; // 平衡因子AVLTreeNode(const pair<K,V>& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr),_bf(0){}
};
template<class K,class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public:AVLTree() = default;~AVLTree(){Destory(_root);_root = nullptr;}AVLTree(const AVLTree<K,V>& kv){_root = Copy(kv._root);}AVLTree operator=(AVLTree<K, V> kv){swap(_root, kv._root);}bool Insert(const pair<K, V>& kv){// 若_root为空,直接让新插入的结点变成根if (_root == nullptr){_root = new Node(kv);return true;}// 若_root不为空,按二叉搜索树的规则找到插入的位置,同时也要记录父亲结点Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else{return false;}}// 找到要插入的位置后,根据传过来的值创建一个结点,然后判断其是父亲节点的左边还是右边,链接上去cur = new Node(kv);if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}// 因为现在是三叉链结果,还要链接一下父亲结点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){// 不平衡了,旋转处理if (parent->_bf == 2 && parent->_right->_bf == 1){RotateL(parent);}else if (parent->_bf == -2 && parent->_left->_bf == -1){RotateR(parent);}else if (parent->_bf == 2 && parent->_right->_bf == -1){RotateRL(parent);}else{RotateLR(parent);}break; // 旋转完后,一定平衡了,所以可以跳出循环}else{assert(false);}}return true;}void InOrder(){_InOrder(_root);}bool IsBalanceTree(){return _IsBalanceTree(_root);}int Height(){return _Height(_root);}int Size(){_Size(_root);}
private:int _Size(Node* root){return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;}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;// 计算pRoot节点的平衡因子:即pRoot左右子树的高度差int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);int diff = rightHeight - leftHeight;// 如果计算出的平衡因子与pRoot的平衡因子不相等,或者// pRoot平衡因子的绝对值超过1,则一定不是AVL树if (abs(diff) >= 2 || root->_bf != diff)return false;// pRoot的左和右如果都是AVL树,则该树一定是AVL树return _IsBalanceTree(root->_left) && _IsBalanceTree(root -> _right);}void RotateL(Node* parent) // 左单旋{Node* subR = parent->_right;Node* subRL = subR->_left;Node* parentParent = parent->_parent;// 1. 将subRL变成parent的右子树parent->_right = subRL;if (subRL)subRL->_parent = parent;// 2. 将parent变成subR的左子树subR->_left = parent;parent->_parent = subR;// 3. 建立subR与parent的父亲结点parentParent的关系if (parentParent == nullptr) // 说明parent就是根结点{_root = subR;subR->_parent = nullptr;}else{if (parent == parentParent->_left)parentParent->_left = subR;elseparentParent->_right = subR;subR->_parent = parentParent;}// 4. 更新parent和subR的平衡因子?parent->_bf = subR->_bf = 0;}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;Node* parentParent = parent->_parent;// 1. 将subLR变成parent的左子树parent->_left = subLR;if (subLR)subLR->_parent = parent;// 2. 将parent变成subL的右子树subL->_right = parent;parent->_parent = subL;// 3.?建立subL与parent的父亲结点parentParent的关系if (parentParent == nullptr) // 说明parent就是根结点{_root = subL;subL->_parent = nullptr;}else{if (parentParent->_left == parent)parentParent->_left = subL;elseparentParent->_right = subL;subL->_parent = parentParent;}// 4.更新parent和subL的平衡因子parent->_bf = subL->_bf = 0;}void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;// 1、计算subRL的平衡因子int bf = subRL->_bf;// 2、对subR进行右单旋RotateR(subR);// 3、对parent进行左单旋RotateL(parent);// 4、更新parent、subR、subRL的平衡因子if (bf == 0){subR->_bf = 0;subRL->_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);}}void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;// 1. 计算subLR的平衡因子int bf = subLR->_bf;// 2. 对subL进行左单旋RotateL(subL);// 3. 对parent进行右单旋RotateR(parent);// 4.?更新parent、subR、subRL的平衡因子if (bf == 0){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 0;}else if (bf == 1){subL->_bf = -1;subLR->_bf = 0;parent->_bf = 0;}else if (bf == -1){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 0;}else{assert(false);}}void Destory(Node* root){if (root == nullptr) return;Destory(root->_left);Destory(root->_right);delete root;}Node* Copy(Node* root){if (root == nullptr) return nullptr;Node* newNode = new Node(root->_kv);newNode->_left = Copy(root->_left);newNode->_right = Copy(root->_right);return newNode;}void _InOrder(Node* _root){if (_root == nullptr) return;_InOrder(_root->_left);cout << _root->_kv.first << "--" << _root->_kv.second << "--" << _root->_bf << endl;_InOrder(_root->_right);}Node* _root;
};