AVL树是一种高度平衡的二叉搜索树,它的每个结点都有一个平衡因子,这个平衡因子的取值是-1,0,1(平衡因子 = 右子树高度 – 左子树高度)
AVL树具有的性质:
1.左子树和右子树的高度差不超过1;
2.树中的各子树都是AVL树
一、AVL树结点的定义
为了判断每个子树是否平衡,因此在定义AVL树的结点的时候就应该在搜索二叉树的基础上加一个平衡因子_bf
template<class K,class V>
struct AVLTreeNode
{
AVLTreeNode(const K& key,const V& value)
:_left(NULL)
,_right(NULL)
,_parent(NULL)
,_key(key)
,_value(value)
,_bf(0)
{}
AVLTreeNode<K,V>* _left;
AVLTreeNode<K,V>* _right;
AVLTreeNode<K,V>* _parent;
K _key;
V _value;
int _bf; //平衡因子
};
二、AVL树的操作—插入
①当插入结点的key与某个结点的_key相等时,插入失败,返回false;
②每插入一个结点,就应该检查是否平衡,判断的依据就是_bf的值不超过1
a.当插入一个结点后,父亲结点的_bf由-1/1变为0,说明在插入之前父亲节点有一个子树,此时整棵树的高度不变
b.当插入一个结点后,父亲结点的_bf由0变为-1/1,说明在插入之前父亲节点没有子树,此时整棵树的高度一定变
如上图所示的这种情况插入一个结点后整棵树仍然保持平衡的,但如果此时以cur作为父亲节点,插入一个结点后还会平衡嘛?!
此时的ppNode的平衡因子已经变为-2,显然已经不满足AVL树的性质。为了解决这种情况,就要对树进行旋转,以达到平衡。
(PS:我对旋转这块的内容写了一篇博客,在这里就不写了,详情请戳这里>>平衡搜索树中的左单旋&右单旋&双旋)
插入的几种情况总结起来就是:
当parent->_bf变为0,就停止更新(平衡因子);当parent->_bf变为-1/1,继续向上更新平衡因子;当parent->_bf变为-2/2,进行旋转
bool Insert(const K& key, const V& value)
{
if (_root == NULL)
{
_root = new Node(key,value);
return true;
}
Node* cur = _root;
Node* parent = NULL;
while (cur)
{
if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else if(cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else
{
//已经有key,插入失败
return false;
}
}
cur = new Node(key,value);
cur->_parent = parent;
if (cur->_key < parent->_key)
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}
//插入新的结点后,检查是否满足平衡二叉树,不满足就进行旋转
while(parent)
{
if (parent->_left == cur) //新增结点在左,_bf--
{
parent->_bf --;
}
else if(parent->_right == cur) //新增结点在右,_bf++
{
parent->_bf ++;
}
//判断父亲的平衡因子,若parent->_bf为2、-2,就进行旋转
if (parent->_bf == 0)
{
break; //parent的平衡因子为0,说明这棵树一定满足平衡二叉树
}
else if (parent->_bf == -1 || parent->_bf == 1)
{
//继续更新平衡因子
cur = parent;
parent = cur->_parent;
}
else //parent->_bf == -2 / 2
{
if (parent->_bf == -2)
{
if(cur->_bf == -1)
RotateRight(parent);
else
RotateLR(parent);
}
else
{
if (cur->_bf == -1)
RotateRL(parent);
else
RotateLeft(parent);
}
}
}
return true;
}
旋转:
void RotateLeft(Node* parent) //左单旋
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL; //先改变parent的右指针
if (subRL) //subRL可能为NULL
{
subRL->_parent = parent;
}
Node* ppNode = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
if (ppNode == NULL)
{
_root = subR;
subR->_parent = NULL;
}
else
{
//判断subR应链接在ppNode的左子树还是右子树
if (ppNode->_left == parent)
ppNode->_left = subR;
else
ppNode->_right = subR;
subR->_parent = ppNode;
}
subR->_bf = parent->_bf = 0;
}
void RotateRight(Node* parent) //右单旋
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR)
{
subLR->_parent = parent;
}
Node* ppNode = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
if (ppNode == NULL) //说明parent结点为根节点
{
_root = subL;
subL->_parent = NULL;
}
else
{
//如果parent不为根节点,判断其在上一个结点的右还是左
if (ppNode->_left == parent)
ppNode->_left = subL;
else
ppNode->_right = subL;
subL->_parent = ppNode;
}
subL->_bf = parent->_bf = 0;
}
void RotateLR(Node* parent) //左右双旋
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotateLeft(parent->_left);
RotateRight(parent);
if (bf == 0) //subLR本身就是新增节点
{
parent->_bf = subL->_bf = subLR->_bf = 0;
}
else if(bf == 1) //subLR的右子树是新增节点
{
parent->_bf = 0;
subL->_bf = -1;
subLR->_bf = 0;
}
else //bf == -1----subLR的左子树是新增结点
{
parent->_bf = 1;
subL->_bf = 0;
subLR->_bf = 0;
}
}
void RotateRL(Node* parent) //右左双旋
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;
RotateRight(parent->_right);
RotateLeft(parent);
if (bf == 0) //subRL本身就是新增节点
{
parent->_bf = subR->_bf = subRL->_bf = 0;
}
else if(bf == 1) //subRL的右子树是新增节点
{
parent->_bf = -1;
subR->_bf = 0;
subRL->_bf = 1;
}
else //bf == -1----subR L的左子树是新增结点
{
parent->_bf = 0;
subR->_bf = 1;
subRL->_bf = -1;
}
}
三、AVL树的效率
假设一棵AVL树有N个结点,那么它的高度可以保持在lgN(log以2为底的N的对数),
插入时最好的情况就是第一次就能找到这个结点的位置进行插入,时间复杂度为O(1);最坏的情况是需要遍历整棵树的高度次,时间复杂度为O(lgN)