之前详细讲解过了二叉搜索树(二叉搜索树详解博客链接:https://blog.youkuaiyun.com/smx_dd/article/details/86563142),但是二叉搜索树有缺陷,那就是当插入数据有序时,会退化成单支树,查找效率相当于一个链表。为了解决上述问题,有这样一种办法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
什么是AVL树? AVL树是一颗空树,或者是具有以下性质的一棵二叉搜索树:1. 它的左右子树都是AVL树。 2. 左右子树高度之差的绝对值不超过1。
关于AVL树节点的定义,与普通二叉树不同,这里我们引入的指向其父亲结点的指针和平衡因子。
template<class K,class V>
struct AVLNode
{
AVLNode(const pair<K,V>& _kv)
:left(nullptr)
, right(nullptr)
, parent(nullptr)
, kv(_kv)
, bf(0)
{}
AVLNode<K,V>* left;
AVLNode<K, V>* right;
AVLNode<K, V>* parent;
pair<K,V> kv;
int bf;
};
AVL树的插入
首先AVL是一颗二叉搜索树,所以插入规则和二叉搜索树规则一样,先是寻找合适的位置:从根结点开始,比较要插入节点的 val 和当前节点的 val 大小,如果当前节点的 val 小于要插入节点的 val ,则从当前节点的右孩子去寻找;如果当前节点的 val 大于要插入节点的 val ,则从当前节点的左孩子去寻找。
按照二叉搜索树的插入方式插入新节点之后,该AVL树的平衡性可能会被破坏,此时需要更新平衡因子,并检测是否破坏了平衡性。
在新节点插入之前,插入之后的节点的父亲节点 parent 的平衡因子一定是 -1,0,1。
1. 如果新节点插入到 parent 的左孩子,则 parent 的平衡因子减 1。
2. 如果新节点插入到 parent 的右孩子,则 parent 的平衡因子加 1。
插入新节点后,parent 的平衡因子有以下几种情况: 0,正负1,正负2。
1. 如果插入新节点后 parent 的平衡因子为 0,则无需修改,满足AVL树。
2. 如果插入新节点后 parent 的平衡因子为正负1,说明插入前 parent 平衡因子为0,此时以 parent 为根的树高度增加,需要将
parent 的 parent节点继续更新,即向上更新。
3. 如果插入新节点后 parent 的平衡因子为正负2,此时不满足 AVL树的性质,需要进行旋转处理。 这种情况只有第一次为情况2时,向上更新后,parent节点(其实是插入节点的祖父节点)出现的情况。一旦出现该情况之后,旋转之后便无需再继续向上更新,跳出循环更新即可。
该部分代码结构框架如下:
while(parent)
{
if(parent->left==cur)
{
--parent->bf;
}
else
{
++parent->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)
{
//相关操作
break;
}
else
{
assert(false);
}
}
AVL树的旋转
上面已经讲到,当插入新节点后,parent节点的平衡因子变为正负2时,已经不满足AVL树的平衡性,需要我们进行旋转操作。
旋转分为四种情况:左单旋、右单旋、左右双旋和右左双旋。分别对应以下插入情况:
左单旋:
void rotateL(Node* _parent)
{
Node* subR = _parent->right;
Node* subRL = _parent->right->left;
_parent->right = subRL; //
if (subRL)
{
subRL->parent = _parent;
}
subR->left = _parent;
Node* pNode = _parent->parent; //记录parent节点的父节点
_parent->parent = subR;
if (_parent == root) //如果parent为树的根节点,需要修改根节点
{
subR->parent = nullptr;
root = subR;
}
else //如果不是,则需要将subR插入到 parent的父节点的左边或者右边
{
if (pNode->left == _parent)
{
pNode->left = subR;
}
else
{
pNode->right = subR;
}
subR->parent = pNode;
}
_parent->bf = 0; //更新平衡因子,都置为0
subR->bf = 0;
}
右单旋:
void rotateR(Node* _parent)
{
Node* subL = _parent->left;
Node* subLR = _parent->left->right;
_parent->left = subLR;
if (subLR)
{
subLR->parent = _parent;
}
subL->right = _parent;
Node* pNode = _parent->parent;
_parent->parent = subL;
if (_parent == root)
{
subL->parent = nullptr;
root = subL;
}
else
{
if (pNode->left == _parent)
{
pNode->left = subL;
}
else
{
pNode->right = subL;
}
subL->parent = pNode;
}
subL->bf = 0;
_parent->bf = 0;
}
左右双旋:
双旋的过程可以用两个单旋来完成,只不过需要修改平衡因子,修改平衡因子又分两种情况:
1. 若旋转之前最底下节点的平衡因子为-1:此时将subL平衡因子修改为0,将parent平衡因子修改为1。
2. 若旋转之前最底下节点平衡因子为1:将subL平衡因子修改为-1,parent平衡因子修改为0。
Node* subL = parent->left;
Node* subLR = parent->left->right;
int _bf = subLR->bf;
rotateL(parent->left);
rotateR(parent);
if (_bf == -1)
{
parent->bf = 1;
subL->bf = 0;
}
else if (_bf == 1)
{
parent->bf = 0;
subL->bf = -1;
}
else
{
parent->bf = 0;
subL->bf = 0;
}
右左双旋:
双旋的过程通过两个单旋来完成,需要修改平衡因子
1. 旋转之前最底下节点平衡因子为-1:此时将subR平衡因子设置为1,parent平衡因子设置为0。
2. 旋转之前最底下节点平衡因子为1:此时将subR平衡因子修改为0,parent平衡因子修改为-1。
Node* subR = parent->right;
Node* subRL = parent->right->left;
int _bf = subRL->bf;
rotateR(parent->right);
rotateL(parent);
if (_bf == -1)
{
parent->bf = 0;
subR->bf = 1;
}
else if (_bf == 1)
{
parent->bf = -1;
subR->bf = 0;
}
else
{
parent->bf = 0;
subR->bf = 0;
}
通过上面的讲解我们可以得到一个完整的插入函数:
bool insert(const pair<K,V>& _kv)
{
if (root == nullptr)
{
root = new Node(_kv);
return true;
}
Node* cur = root;
Node* parent = nullptr;
while (cur)
{
if (cur->kv.first > _kv.first)
{
parent = cur;
cur = cur->left;
}
else if (cur->kv.first < _kv.first)
{
parent = cur;
cur = cur->right;
}
else
{
return false;
}
}
cur = new Node(_kv);
if (cur->kv.first < parent->kv.first)
{
parent->left = cur;
cur->parent = parent;
}
else
{
parent->right = cur;
cur->parent = parent;
}
while (parent)
{
if (cur == parent->left)
{
--parent->bf;
}
else
{
++parent->bf;
}
if (parent->bf == -1 || parent->bf == 1)
{
cur = parent;
parent = parent->parent;
}
else if (parent->bf == 0)
{
break;
}
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) //左右双旋
{
Node* subL = parent->left;
Node* subLR = parent->left->right;
int _bf = subLR->bf;
rotateL(parent->left);
rotateR(parent);
if (_bf == -1)
{
parent->bf = 1;
subL->bf = 0;
}
else if (_bf == 1)
{
parent->bf = 0;
subL->bf = -1;
}
else
{
parent->bf = 0;
subL->bf = 0;
}
}
else if (parent->bf == 2 && cur->bf == -1) //右左双旋
{
Node* subR = parent->right;
Node* subRL = parent->right->left;
int _bf = subRL->bf;
rotateR(parent->right);
rotateL(parent);
if (_bf == -1)
{
parent->bf = 0;
subR->bf = 1;
}
else if (_bf == 1)
{
parent->bf = -1;
subR->bf = 0;
}
else
{
parent->bf = 0;
subR->bf = 0;
}
}
break; //旋转结束后无需继续修改。
}
else
{
assert(false);
}
}
}
AVL树完整实现(插入,旋转,中序遍历,判断是否平衡,求树的高度)
#include<iostream>
#include<assert.h>
using namespace std;
template<class K,class V>
struct AVLNode
{
AVLNode(const pair<K,V>& _kv)
:left(nullptr)
, right(nullptr)
, parent(nullptr)
, kv(_kv)
, bf(0)
{}
AVLNode<K,V>* left;
AVLNode<K, V>* right;
AVLNode<K, V>* parent;
pair<K,V> kv;
int bf;
};
template <class K,class V>
class AVLTree
{
typedef AVLNode<K, V> Node;
public:
bool insert(const pair<K,V>& _kv)
{
if (root == nullptr)
{
root = new Node(_kv);
return true;
}
Node* cur = root;
Node* parent = nullptr;
while (cur)
{
if (cur->kv.first > _kv.first)
{
parent = cur;
cur = cur->left;
}
else if (cur->kv.first < _kv.first)
{
parent = cur;
cur = cur->right;
}
else
{
return false;
}
}
cur = new Node(_kv);
if (cur->kv.first < parent->kv.first)
{
parent->left = cur;
cur->parent = parent;
}
else
{
parent->right = cur;
cur->parent = parent;
}
while (parent)
{
if (cur == parent->left)
{
--parent->bf;
}
else
{
++parent->bf;
}
if (parent->bf == -1 || parent->bf == 1)
{
cur = parent;
parent = parent->parent;
}
else if (parent->bf == 0)
{
break;
}
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) //左右双旋
{
Node* subL = parent->left;
Node* subLR = parent->left->right;
int _bf = subLR->bf;
rotateL(parent->left);
rotateR(parent);
if (_bf == -1)
{
parent->bf = 1;
subL->bf = 0;
}
else if (_bf == 1)
{
parent->bf = 0;
subL->bf = -1;
}
else
{
parent->bf = 0;
subL->bf = 0;
}
}
else if (parent->bf == 2 && cur->bf == -1) //右左双旋
{
Node* subR = parent->right;
Node* subRL = parent->right->left;
int _bf = subRL->bf;
rotateR(parent->right);
rotateL(parent);
if (_bf == -1)
{
parent->bf = 0;
subR->bf = 1;
}
else if (_bf == 1)
{
parent->bf = -1;
subR->bf = 0;
}
else
{
parent->bf = 0;
subR->bf = 0;
}
}
break;
}
else
{
assert(false);
}
}
}
void rotateR(Node* _parent)
{
Node* subL = _parent->left;
Node* subLR = _parent->left->right;
_parent->left = subLR;
if (subLR)
{
subLR->parent = _parent;
}
subL->right = _parent;
Node* pNode = _parent->parent;
_parent->parent = subL;
if (_parent == root)
{
subL->parent = nullptr;
root = subL;
}
else
{
if (pNode->left == _parent)
{
pNode->left = subL;
}
else
{
pNode->right = subL;
}
subL->parent = pNode;
}
subL->bf = 0;
_parent->bf = 0;
}
void rotateL(Node* _parent)
{
Node* subR = _parent->right;
Node* subRL = _parent->right->left;
_parent->right = subRL;
if (subRL)
{
subRL->parent = _parent;
}
subR->left = _parent;
Node* pNode = _parent->parent;
_parent->parent = subR;
if (_parent == root)
{
subR->parent = nullptr;
root = subR;
}
else
{
if (pNode->left == _parent)
{
pNode->left = subR;
}
else
{
pNode->right = subR;
}
subR->parent = pNode;
}
_parent->bf = 0;
subR->bf = 0;
}
void inorder()
{
_inorder(root);
cout << endl;
}
bool isbalance()
{
return _isbalance(root);
}
private:
Node* root = nullptr;
void _inorder(Node* root)
{
if (root == nullptr)
{
return;
}
_inorder(root->left);
cout << root->kv.first << " ";
_inorder(root->right);
}
int _deep(Node* root)
{
if (root == nullptr)
{
return 0;
}
int left = _deep(root->left);
int right = _deep(root->right);
return left > right ? left + 1 : right + 1;
}
bool _isbalance(Node* root)
{
if (root == nullptr)
{
return true;
}
int leftdeep = _deep(root->left);
int rightdeep = _deep(root->right);
int dis = rightdeep - leftdeep;
if (dis != root->bf)
{
cout << "节点平衡因子错误" << endl;
return false;
}
if (dis > 1 || dis < -1)
{
return false;
}
else
{
return _isbalance(root->left) && _isbalance(root->right);
}
}
};
int main()
{
int arr[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
//int arr[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
AVLTree<int, int> tree;
for (auto e : arr)
{
tree.insert(make_pair(e, 0));
//tree.insert(pair<int, int>(e, 0));
cout << e << "---" << tree.isbalance() << endl;
}
tree.inorder();
system("pause");
return 0;
}
AVL树性能分析
AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即O(log2N)。但是如果要对AVL树做一些结构修改的操作,性能非常低下。
比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑 AVL 树,但一个结构经常修改,就不太适合。