目录
1、AVL树的概念
AVL树又被称为平衡二叉搜索书,它是俄罗斯的两位数学家G.M. Adelson-Velsky 和 E.M. Landis名字的缩写。
搜索二叉树虽然可以缩短查找的效率,但如果当一组数据有序或接近有序时,这会导致搜索二叉树退化成单支树,查找的效率就会相当于在顺序表中查找的效率,效率低下。AVL树的出现就是为了解决这一问题:当向二叉树中插入一个新节点后,要保证该树中每个节点的左右子树的高度差不超过1,这就可以降低树的高度,从而减少搜索长度。
AVL树本质上是一棵二叉搜索树,但它还具有以下性质:
- 它的左右子树都是AVL树
- 左右子树的最大高度差(简称平衡因子,右子树的最大高度-左子树的最大高度)不超过1(-1/0/1)
2、AVL树的基本操作
2.1创建一个节点
由于AVL树的底层是一棵二叉搜索树,因此AVL树的节点内存储的变量与普通二叉搜索数的变量基本相似,但是由于该树要构成一颗三叉链,因此内部要存储一个父节点的指针。为了平衡因子计算的方便,可以在该节点中加入一个整形用来存储平衡因子,但这并不是必须的
template<class T>
struct AVLTreeNode
{
AVLTreeNode(const T& val)
:_left(nullptr), _right(nullptr)
, _parent(nullptr), _val(val), _bf(0) //一个新节点左右都为空,故平衡因子初始化为0
{ }
AVLTreeNode* _left; //节点的左孩子
AVLTreeNode* _right; //节点的右孩子
AVLTreeNode* _parent; //节点的父亲
T _val;
int _bf; //该节点的平衡因子
};
2.2AVL树的插入
AVL树的插入就是在二叉搜索树插入的基础上引入了平衡因子,因此AVL树的插入可以分为两部分:
- 按照二叉搜索树的方式插入一个新的节点
- 调整节点的平衡因子
2.2.1按照二叉搜索树的方式插入一个新的节点
bool insert(const T& val)
{
if (_root == nullptr)
{
_root = new Node(val);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur) //直到cur == nullptr循环结束
{
if (val > cur->_val)
{
parent = cur;
cur = cur->_right;
}
else if (val < cur->_val)
{
parent = cur;
cur = cur->_left;
}
else //val == cur->_val,插入失败
{
return false;
}
}
cur = new Node(val);
if (cur->_val > parent->_val)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
cur->_parent = parent;
}
2.2.2调整节点的平衡因子
插入新节点后可能会导致其父节点的平衡因子发生改变,在插入新节点前,父节点的平衡因子有以下三种情况:-1/0/1,插入新节点后,父节点的平衡因子变化有两种方式:
- 插入的节点位于父节点的左边,父节点的平衡因子-1
- 插入的节点位于父节点的右边,父节点的平衡因子+1
插入新节点后会导致其父节点的平衡因子有以下几种情况:-2/-1/0/1/2
- 如果此时父节点的平衡因子为0,这说明在插入新节点前父节点的平衡因子为1或-1,插入后被调整为0,这表明该树的高度没有改变,且满足AVL树的性质,因此不需要进行调整
- 如果此时父节点的平衡因子为-1或1,这说明在插入新节点前父节点的平衡因子为0,插入后被调整为-1或1,这说明树的高度发生了改变,因此需要继续向上更新平衡因子,直到平衡因子再次为0或更新至根节点
- 如果此时父节点的平衡因子为-2或2,这说明在插入新节点前父节点的平衡因子为-1或1,插入后被调整为-2或2,这说明树的高度发生了改变,且由于该树不符合AVL树的性质,因此需要对其进行旋转处理
2.2.3旋转
AVL树的旋转分为单旋和双旋,单旋被分为左单旋和右单旋,双旋被分为左右双旋和右左双旋
2.2.3.1左单旋

从上图中我们可以看到,当在c这棵树中插入节点时,c的高度变成了h+1,此时60这个节点的平衡因子变为1,故需要向上传递,30的平衡因子就会变成2,这时就会导致这棵树不符合AVL树的性质,因此会触发旋转,由于高的一侧位于树的右边所以要进行左单旋。左单旋就是使60这个节点变为这棵树根节点,30的右子树的指针指向60的左子树b,60的左子树的指针指向30这个节点,这样就完成了一次左单旋,旋转结束后要记得更新平衡因子。
在节点30的右节点的右子树上插入新的节点,我们成这种情况叫做右右型,要用到左旋转
在旋转过程中要考虑一下几个事项:
- 60的左孩子可能存在,也可能不存在,因此在调整指针指向时要特别注意_parent指针
- 30可能是根节点,也可能是一棵子树。如果是根节点,在旋转结束后要及时更新根节点的指向;如果是一棵子树,就要判断这棵子树是在左子树还是在右子树
//if (parent->_bf == 2 && cur->_bf == 1) //新节点位于右节点的右子树
//{
// RotateL(parent);
//}
void RotateL(Node* parent)
{
Node* subr = parent->_right;
Node* subrl = subr->_left;
Node* pparent = parent->_parent;
parent->_right = subrl;
if (subrl)
subrl->_parent = parent;
subr->_left = parent;
parent->_parent = subr;
if (_root == parent)
{
_root = subr;
subr->_parent = nullptr;
}
else
{
if (pparent->_left == parent)
{
pparent->_left = subr;
}
else
{
pparent->_right = subr;
}
subr->_parent = pparent;
}
parent->_bf = subr->_bf = 0;
}
2.2.3.2右单旋

从上图中我们可以看到,当在a这棵树中插入节点时,a的高度变成了h+1,此时30这个节点的平衡因子变为-1,故需要向上传递,60的平衡因子就会变成-2,这时就会导致这棵树不符合AVL树的性质,因此会触发旋转,由于高的一侧位于树的左边所以要进行右单旋。右单旋就是使30这个节点变为这棵树根节点,60的左子树的指针指向30的右子树b,30的右子树的指针指向60这个节点,这样就完成了一次右单旋,旋转结束后要记得更新平衡因子。
在节点60的左节点的左子树上插入新的节点,我们成这种情况叫做左左型,要用到右旋转
在旋转过程中要考虑一下几个事项:
- 30的右孩子可能存在,也可能不存在,因此在调整指针指向时要特别注意_parent指针
- 60可能是根节点,也可能是一棵子树。如果是根节点,在旋转结束后要及时更新根节点的指向;如果是一棵子树,就要判断这棵子树是在左子树还是在右子树
//else if (parent->_bf == -2 && cur->_bf == -1)
//{
// RotateR(parent);
//}
void RotateR(Node* parent)
{
Node* subl = parent->_left;
Node* sublr = subl->_right;
Node* pparent = parent->_parent;
parent->_left = sublr;
if (sublr)
sublr->_parent = parent;
subl->_right = parent;
parent->_parent = subl;
if (_root == parent)
{
_root = subl;
subl->_parent = nullptr;
}
else
{
if (pparent->_left == parent)
{
pparent->_left = subl;
}
else
{
pparent->_right = subl;
}
subl->_parent = pparent;
}
parent->_bf = subl->_bf = 0;
}
2.2.3.3左右双旋

从上图我们可以看到在90这个节点的左节点的右子树上插入一个新节点导致90的平衡因子为2,因此这里我们就需要进行旋转,但是我们发现只进行一次右旋无法使这棵树符合AVL树的性质,因此我们这里就需要对这棵树进行双旋处理。这里一共分成两步进行:
- 以30为中心进行左旋,这样使60的左树接到了30的右边,这就使得这棵树符合左旋的条件,即相当于在90的左节点的左子树上插入了一个节点,这里我们就进行第二步
- 以60为中心进行右旋,详细步骤可以看上面
在节点90的左节点的右子树上插入一个新节点,我们将这种情况称为左右型,因此我们需要进行左右双旋
在旋转前,需要保存60这个节点的平衡因子,因为旋转结束后,需要根据该平衡因子去调整其他节点的平衡因子
void RotateLR(Node* parent)
{
Node* subl = parent->_left;
Node* sublr = subl->_right;
int bf = sublr->_bf;
RotateL(subl);
RotateR(parent);
if (bf == 1)
{
parent->_bf == 0;
sublr->_bf = 0;
subl->_bf = -1;
}
else if (bf == -1)
{
parent->_bf == 1;
sublr->_bf = 0;
subl->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = sublr->_bf = subl->_bf = 0;
}
else
{
assert(false);
}
}
2.2.3.4右左双旋

从上图我们可以看到在30这个节点的右节点的左子树上插入一个新节点导致30的平衡因子为2,因此这里我们就需要进行旋转,但是我们发现只进行一次左旋无法使这棵树符合AVL树的性质,因此我们这里就需要对这棵树进行双旋处理。这里一共分成两步进行:
- 以90为中心进行左旋,这样使60的右树接到了90的左边,这就使得这棵树符合右旋的条件,即相当于在30的右节点的右子树上插入了一个节点,这里我们就进行第二步
- 以30为中心进行左旋,详细步骤可以看上面
在节点30的右节点的左子树上插入一个新节点,我们将这种情况称为右左型,因此我们需要进行右左双旋
在旋转前,需要保存60这个节点的平衡因子,因为旋转结束后,需要根据该平衡因子去调整其他节点的平衡因子
void RotateRL(Node* parent)
{
Node* subr = parent->_right;
Node* subrl = subr->_left;
int bf = subrl->_bf;
RotateR(subr);
RotateL(parent);
if (bf == 1)
{
parent->_bf == -1;
subrl->_bf = 0;
subr->_bf = 0;
}
else if (bf == -1)
{
parent->_bf == 0;
subrl->_bf = 0;
subr->_bf = 1;
}
else if (bf == 0)
{
parent->_bf = subrl->_bf = subr->_bf = 0;
}
else
{
assert(false);
}
}
2.2.3.5总结
| 插入位置 | 状态 | 操作 |
| 父节点的左节点的左子树 | 左左型 | 右旋 |
| 父节点的左节点的右子树 | 左右型 | 左右旋 |
| 父节点的右节点的右子树 | 右右型 | 左旋 |
| 父节点的右节点的左子树 | 右左型 | 右左旋 |
3、检查是否为AVL树
检查一棵树是否为AVL树不能直接去查它的平衡因子,这是因为你不确定平衡因子是否正确,且这棵树不一定存储了平衡因子。因此,要去检查这棵树是否为AVL树可以通过计算这棵树左子树和右子树高度的差值进行检验。
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 _IsBalance(Node* root)
{
if (root == nullptr)
{
return true;
}
int leftHeight = Height(root->_left);
int rightHeight = Height(root->_right);
if (rightHeight - leftHeight != root->_bf)
{
cout << "平衡因子异常" << endl;
return false;
}
return abs(rightHeight - leftHeight) < 2
&& _IsBalance(root->_left) && _IsBalance(root->_right); //每棵子树同样需要是AVL树
}
4、总代码
template<class T>
struct AVLTreeNode
{
AVLTreeNode(const T& val)
:_left(nullptr), _right(nullptr)
, _parent(nullptr), _val(val), _bf(0)
{
}
AVLTreeNode* _left; //节点的左孩子
AVLTreeNode* _right; //节点的右孩子
AVLTreeNode* _parent; //节点的父亲
T _val;
int _bf; //该节点的平衡因子
};
template<class T>
class AVLTree
{
typedef AVLTreeNode<T> Node;
public:
AVLTree()
:_root(nullptr)
{
}
bool insert(const T& val)
{
if (_root == nullptr)
{
_root = new Node(val);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur) //直到cur == nullptr循环结束
{
if (val > cur->_val)
{
parent = cur;
cur = cur->_right;
}
else if (val < cur->_val)
{
parent = cur;
cur = cur->_left;
}
else //val == cur->_val,插入失败
{
return false;
}
}
cur = new Node(val);
if (cur->_val > parent->_val)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
cur->_parent = parent;
while (parent)
{
if (parent->_left == cur)
{
parent->_bf--;
}
else
{
parent->_bf++;
}
if (parent->_bf == 0) //bf为0,该树高度不变,不需要调整
{
break;
}
else if (parent->_bf == 1 || parent->_bf == -1) //bf为1或-1,该树高度改变,继续向上调整
{
cur = parent;
parent = parent->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -2) //bf为2或-2,该树高度改变,触发旋转
{
if (parent->_bf == 2 && cur->_bf == 1) //右右型
{
RotateL(parent);
}
else if (parent->_bf == -2 && cur->_bf == -1) //左左型
{
RotateR(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; //旋转结束后,这棵树符合AVL树性质,结束循环
}
else //走到这里说明树的平衡因子异常,超出范围,该树的平衡因子调整错误,对代码重新检查
{
assert(false);
}
}
return true;
}
bool IsBalance()
{
return _IsBalance(_root);
}
private:
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 _IsBalance(Node* root)
{
if (root == nullptr)
{
return true;
}
int leftHeight = Height(root->_left);
int rightHeight = Height(root->_right);
if (rightHeight - leftHeight != root->_bf)
{
cout << "平衡因子异常" << endl;
return false;
}
return abs(rightHeight - leftHeight) < 2
&& _IsBalance(root->_left) && _IsBalance(root->_right);
}
void RotateRL(Node* parent)
{
Node* subr = parent->_right;
Node* subrl = subr->_left;
int bf = subrl->_bf;
RotateR(subr);
RotateL(parent);
if (bf == 1)
{
parent->_bf = -1;
subrl->_bf = 0;
subr->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 0;
subrl->_bf = 0;
subr->_bf = 1;
}
else if (bf == 0)
{
parent->_bf = subrl->_bf = subr->_bf = 0;
}
else
{
assert(false);
}
}
void RotateLR(Node* parent)
{
Node* subl = parent->_left;
Node* sublr = subl->_right;
int bf = sublr->_bf;
RotateL(subl);
RotateR(parent);
if (bf == 1)
{
parent->_bf = 0;
sublr->_bf = 0;
subl->_bf = -1;
}
else if (bf == -1)
{
parent->_bf = 1;
sublr->_bf = 0;
subl->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = sublr->_bf = subl->_bf = 0;
}
else
{
assert(false);
}
}
void RotateR(Node* parent)
{
Node* subl = parent->_left;
Node* sublr = subl->_right;
Node* pparent = parent->_parent;
parent->_left = sublr;
if (sublr)
sublr->_parent = parent;
subl->_right = parent;
parent->_parent = subl;
if (_root == parent)
{
_root = subl;
subl->_parent = nullptr;
}
else
{
if (pparent->_left == parent)
{
pparent->_left = subl;
}
else
{
pparent->_right = subl;
}
subl->_parent = pparent;
}
parent->_bf = subl->_bf = 0;
}
void RotateL(Node* parent)
{
Node* subr = parent->_right;
Node* subrl = subr->_left;
parent->_right = subrl;
subr->_left = parent;
Node* pparent = parent->_parent;
parent->_parent = subr;
if (subrl)
subrl->_parent = parent;
if (_root == parent)
{
_root = subr;
subr->_parent = nullptr;
}
else
{
if (pparent->_left == parent)
{
pparent->_left = subr;
}
else
{
pparent->_right = subr;
}
subr->_parent = pparent;
}
parent->_bf = subr->_bf = 0;
}
Node* _root;
};

2006

被折叠的 条评论
为什么被折叠?



