万字详解AVL树
一、AVL树概念
AVL树,又称为平衡二叉树,它基于二叉搜索树并通过平衡而得到。
在前面的学习中我们提到,二叉搜索树可以提高搜索数据的效率,但在数据有序的情况下会退化为单支树,此时在树中查找元素就得遍历一整个分支,时间复杂度也会退化至O(N)。
如果可以采用某种方式使作用两边可以达到平衡,便可以很好的平衡效率问题。
因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了AVL树,解决了上述问题。
二、AVL树性质
AVL树是最先发明的自平衡二叉查找树,AVL是一颗空树,或者具备下列性质的二叉搜索树:它的左右子树都是AVL树,且左右子树的高度差的绝对值不超过1。AVL树是一颗高度平衡搜索二叉树,通过控制高度差去控制平衡
这里规定,在一个结点左边插入结点,该结点平衡因子–,反之则++。
AVL树的性质如下:
- AVL树可以是空树
- 一颗AVL树的左右子树都是AVL树
- 一颗AVL树的左右子树高度差不超过1
左图为AVL树,右图则不是
三、AVL树实现
一个AVL树想要维持住它的平衡,平衡因子肯定是必不可少的,那么,想一想,我们只考虑当前结点的平衡因子吗?上图的情况是不是要考虑到?既然考虑到父亲结点了,那么爷爷结点要不要考虑到?答案是不用的,爷爷结点的调整一定是基于父亲结点和叔叔结点的,所以我们调整时需要一层一层往上调整,只需考虑父亲结点的平衡因子是否符合条件。所以,一个AVL树结点中要包含:指向两个孩子的指针,父亲结点,平衡因子。
struct AVLTreeNode
{// 需要parent指针,后续更新平衡因子可以看到pair<K, V> _kv;AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;int _bf; // balance factorAVLTreeNode(const pair<K, V>& kv):_kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _bf(0){}
};
3.1 插入
AVL树插入过程和二叉搜索树类似,都是和key值进行比较,这里重点讨论:插入数据后如何维持平衡。
当插入数据后会出现两种情况:
- 当前结点父结点平衡因子< ±1,这种情况无需处理
- 当前结点父结点平衡因子>±1,这种情况需要处理
结点平衡因子调整过程和上图类似,一层一层向上调整,直到不用调整为止,但如果某个结点的平衡因子>±1,则不在调整,转而进行处理,根据这种思想,写出代码如下:
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;
}
3.2 旋转
要进行搜索树的平衡,可对结点进行旋转实现。
对于该这个搜索树,如何使其平衡呢?这个时候,进行一个左单旋即可维持平衡。
旋转总共分为四种,左单旋/右单旋/左右双旋/右左双旋。
3.2.1左单旋
关于左单旋发生的场景为:右边高了,为了维持平衡,一定要往左边增加结点,右边减少结点。
这样旋转之后,便达到了平衡且不影响上一层。这里直接用抽象模型进行演示,说明该规律具有普遍性,这里回答一下为什么一定那两个b长度是相同的:如果这两段长度不一致,一长一短,则一定会进行调整因为破坏了AVL的形成条件。
左单旋实现
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 (parent == parentParent->_left){parentParent->_left = subR;}else{parentParent->_right = subR;}subR->_parent = parentParent;}parent->_bf = subR->_bf = 0;
}
3.2.2右单旋
右单旋和左单旋类似,当左边高了,就需要向右旋转,来维持高度平衡。
右单旋代码实现
void RotateR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* pParent = parent->_parent;subL->_right = parent;parent->_parent = subL;if (parent == _root){_root = subL;subL->_parent = nullptr;}else{if (pParent->_left == parent){pParent->_left = subL;}else{pParent->_right = subL;}subL->_parent = pParent;}subL->_bf = 0;parent->_bf = 0;
}
3.2.3左右双旋
针对这棵树,不管进行左/右单旋都无法解决问题,所以便要对其进行双旋,即先对14这个结点进行左旋,对旋转之后的再来一个右旋,即可平衡。
左右双旋代码实现
void RotateLR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent);if (bf == -1){subLR->_bf = 0;subL->_bf = 0;parent->_bf = 1;}else if (bf == 1){subLR->_bf = 0;subL->_bf = -1;parent->_bf = 0;}else if (bf == 0){subLR->_bf = 0;subL->_bf = 0;parent->_bf = 0;}else{assert(false);}
}
3.2.4 右左双旋
这个也是和左右双旋类似
总结:父节点和自己平衡因子符号相同,必定单旋,向平衡因子小的一边旋转;相反则双旋,旋转方法类似。如实在不明白就画图自行推导即可。
左右双旋代码实现
void RotateRL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(parent->_right);RotateL(parent);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);}
}
3.3 查找
查找和二叉搜索树类似,都是二分查找,时间复杂度O(logN)
Node* Find(const K& key)
{Node* cur = _root;while (cur){if (cur->_kv.first < key){cur = cur->_right;}else if (cur->_kv.first > key){cur = cur->_left;}else{return cur;}}return nullptr;
}
3.4 平衡检测
该如何检测这个是一个平衡二叉树呢?能不能通过平衡因子进行检测?当然不可以了,因为平衡因子就是咱们进行规定的,自然符合条件,所以必须提出另外一种检测办法。这里采用高度,计算一个结点两边高度,判断是否大于1即可。
bool _IsBalanceTree(Node* root)
{// 空树也是AVL树if (nullptr == root)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){cout << root->_kv.first << "高度差异常" << endl;return false;}//进行判断平衡因子更新是否正确if (root->_bf != diff){cout << root->_kv.first << "平衡因子异常" << endl;return false;}// pRoot的左和右如果都是AVL树,则该树一定是AVL树return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
}
3.5 有序检测
这里我们采用中序遍历,如若是升序,则该树是有序的
void _InOrder(Node* root)
{if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_InOrder(root->_right);
}
四、完整代码
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;template<class K, class V>
struct AVLTreeNode
{// 需要parent指针,后续更新平衡因子可以看到pair<K, V> _kv;AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;int _bf; // balance factorAVLTreeNode(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: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){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){RotateLR(parent);}else if (parent->_bf == 2 && cur->_bf == -1){RotateRL(parent);}else{assert(false);}break;}else{assert(false);}}return true;}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* pParent = parent->_parent;subL->_right = parent;parent->_parent = subL;if (parent == _root){_root = subL;subL->_parent = nullptr;}else{if (pParent->_left == parent){pParent->_left = subL;}else{pParent->_right = subL;}subL->_parent = pParent;}subL->_bf = 0;parent->_bf = 0;}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 (parent == parentParent->_left){parentParent->_left = subR;}else{parentParent->_right = subR;}subR->_parent = parentParent;}parent->_bf = subR->_bf = 0;}void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent);if (bf == -1){subLR->_bf = 0;subL->_bf = 0;parent->_bf = 1;}else if (bf == 1){subLR->_bf = 0;subL->_bf = -1;parent->_bf = 0;}else if (bf == 0){subLR->_bf = 0;subL->_bf = 0;parent->_bf = 0;}else{assert(false);}}void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(parent->_right);RotateL(parent);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 InOrder(){_InOrder(_root);cout << endl;}int Height(){return _Height(_root);}int Size(){return _Size(_root);}bool IsBalanceTree(){return _IsBalanceTree(_root);}Node* Find(const K& key){Node* cur = _root;while (cur){if (cur->_kv.first < key){cur = cur->_right;}else if (cur->_kv.first > key){cur = cur->_left;}else{return cur;}}return nullptr;}private:void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_InOrder(root->_right);}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;}int _Size(Node* root){if (root == nullptr)return 0;return _Size(root->_left) + _Size(root->_right) + 1;}bool _IsBalanceTree(Node* root){// 空树也是AVL树if (nullptr == root)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){cout << root->_kv.first << "高度差异常" << endl;return false;}if (root->_bf != diff){cout << root->_kv.first << "平衡因子异常" << endl;return false;}// pRoot的左和右如果都是AVL树,则该树一定是AVL树return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);}
private:Node* _root = nullptr;
};
完!