AVL模拟实现以及四种旋转方式图解
AVL树概念
二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。
发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
性质
①左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
②如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,搜索时间复杂度O(log2^n)
模拟实现
首先,我们对AVL树中的节点进行一个封装:
template<class K,class V>
struct AVLTreeNode
{
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent;
pair<K, V> _kv;
int _bf; // balance factor
AVLTreeNode(const pair<K, V>& kv)
: _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _kv(kv)
, _bf(0)
{}
};
AVL的重点操作放在插入节点上,删除节点暂时不涉及。
Insert
插入一个节点第一步找到插入位置,同时比较插入位置cur与parent的大小,来确定是插在parent的左子树上还是右子树上。
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* cur = _root;
Node* parent = nullptr;
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); // cur的位置就是新插入的位置
if (parent->_kv.first > kv.first) // 使得新插入节点和上面的源节点产生连接,先确定插入位置是左孩子还是右孩子
{
parent->_left = cur;
}
else if (parent->_kv.first < kv.first)
{
parent->_right = cur;
}
cur->_parent = parent;
private:
Node* _root = nullptr;
}
接着,因为AVL树是通过平衡因子来控制整棵树始终保持平衡的,所以我们在插入之后要更新平衡因子
默认右子树为正,插入在父亲的左子树平衡因子-1,插入在父亲的右子树平衡因子+1
if (cur == parent->_left) parent->_bf--;
else if (cur == parent->_right) parent->_bf++;
插入之后,我们还得判断这颗树是不是平衡的状态:
如果插入变为0,说明原先为1或-1,新插入节点不影响树的整体高度
如果插入后变为1或-1,说明原先为0,此时树的高度受到了影响,但没失衡,此时继续向上更新观察平衡因子
如果插入后变为2或-2,说明原先为1或-1,此时已经完全失衡了,需要进行旋转
那么具体怎么旋转可以接着往下面看
if (parent->_bf == 0) // 原先为1或-1,插入变为0。说明新插入节点不影响树的整体高度
{
break;
}
else if (parent->_bf == 1 || parent->_bf == -1) // 原先为0,插入后变为1或-1。此时树的高度受到了影响
{
// 继续向上更新
cur = parent;
parent = parent->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -2) // 原先为1或-1,此时已经完全失衡了,需要进行旋转
{
// 此时分为4种情况,要分开讨论
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);
}
break; // 旋转完之后就一定是平衡的状态了,直接break循环
}
新节点插入较高左子树的左侧—左左:右单旋
如果(parent->_bf 等于 -2 && cur->_bf 等于 -1),此时需要进行右旋
抽象图:
很明显,左边严重偏高。呈现抽象图的形状,我们就可以采用右旋来调节了。
我们需要将parent,subL,subLR记录下来,然后将subL作为这棵树新的根节点,把subL的右节点subLR给到parent,让parent的左子树不再是subL而是subLR。
在链接的时候,要注意前后的衔接,同时需要注意两点,①subLR可能为空节点 ②这里所谓的parent可能还有根节点,所以还要对根节点的情况进行判断
这些做完之后就要改变相应的平衡因子,单旋的平衡因子是固定的,通过画图就可以得知。
旋转之后,AVL树就达到了平衡,就不需要再去调节了。
具体代码:
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
Node* ppNode = parent->_parent;
parent->_left = subLR;
if (subLR) subLR->_parent = parent; // 这里判空是因为subLR可能为空节点
subL->_right = parent;
parent->_parent = subL;
if (parent == _root) // 更新根节点
{
_root = subL;
subL->_parent = nullptr; // subL的parent要更新成nullptr
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subL;
}
else if (ppNode->_right == parent)
{
ppNode->_right = subL;
}
subL->_parent = ppNode;
}
parent->_bf = subL->_bf = 0;
}
新节点插入较高右子树的右侧—右右:左单旋
如果(parent->_bf 等于 2 && cur->_bf 等于 1),此时需要进行右旋
抽象图:
很明显,右边严重偏高。呈现抽象图的形状,我们就可以采用左旋来调节了。
我们需要将parent,subR,subRL记录下来,然后将subR作为这棵树新的根节点,把subR的左节点subRL给到parent,让parent的左子树不再是subR而是subRL。
在链接的时候,要注意前后的衔接,同时需要注意两点,①subRL可能为空节点 ②这里所谓的parent可能还有根节点,所以还要对根节点的情况进行判断
这些做完之后就要改变相应的平衡因子,单旋的平衡因子是固定的,通过画图就可以得知。
旋转之后,AVL树就达到了平衡,就不需要再去调节了。
具体代码:
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
Node* ppNode = parent->_parent;
parent->_right = subRL;
if (subRL) subRL->_parent = parent; // 这里判空是因为subRL可能为空节点
subR->_left = parent;
parent->_parent = subR;
if (parent == _root) // 更新根节点
{
_root = subR;
subR->_parent = nullptr; // subR的parent要更新成nullptr
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subR;
}
else if (ppNode->_right == parent)
{
ppNode->_right = subR;
}
subR->_parent = ppNode;
}
parent->_bf = subR->_bf = 0;
}
新节点插入较高左子树的右侧—左右:先左单旋再右单旋
如果(parent->_bf 等于 -2 && cur->_bf 等于 1),此时需要进行双旋,先左单旋再右单旋
抽象图:
呈现抽象图的形状,我们就可以采用先左单旋再右单旋来调节了。
上图写了,双旋的平衡因子结果会受到subLR平衡因子的影响。 subLR的左右节点会分别称为subL的右节点和parent的左节点,影响两者的平衡因子,所以在后续的代码需要进行分情况讨论。
我们需要将parent,subL,subLR记录下来,因为此时的形状用单旋已经达不到效果了,此处我们需要运用到双旋。 我们先对subL进行左旋,接着对parent进行右旋就可以达到效果。
在链接的时候,要注意前后的衔接,同时需要注意两点,①此处,subLR一定不是空节点了,这里与上面的情况不同了,这里是一个弯下来的状态,所以这个subLR这个节点是一定包含在里面的,所以不要判它为是否为空 ②同样的,这里所谓的parent可能还有根节点,所以还要对根节点的情况进行判断
这些做完之后就要改变相应的平衡因子,单旋的平衡因子是固定的,通过画图就可以得知。
旋转之后,AVL树就达到了平衡,就不需要再去调节了。
具体代码:
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf; // 需要提前记录,后面旋转之后会变成0
RotateL(subL);
RotateR(parent);
if (bf == 1)
{
parent->_bf = 0;
subL->_bf = -1;
subLR->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 1;
subL->_bf = 0;
subLR->_bf = 0;
}
else if (bf == 0) // 这种情况属于subLR节点左右节点都有 不存在都没有的情况,因为那样就属于平衡的情况了
{
parent->_bf = 0;
subL->_bf = 0;
subLR->_bf = 0;
}
else
{
assert(false);
}
}
新节点插入较高右子树的左侧—右左:先右单旋再左单旋
如果(parent->_bf 等于 2 && cur->_bf 等于 -1),此时需要进行双旋,先右单旋再左单旋
抽象图:
呈现抽象图的形状,我们就可以采用先右单旋再左单旋来调节了。
上图写了,双旋的平衡因子结果会受到subRL平衡因子的影响。 subRL的左右节点会分别称为parent的右节点和subR的左节点,影响两者的平衡因子,所以在后续的代码需要进行分情况讨论。
我们需要将parent,subR,subRL记录下来,因为此时的形状用单旋已经达不到效果了,此处我们需要运用到双旋。 我们先对subR进行右旋,接着对parent进行左旋就可以达到效果。
在链接的时候,要注意前后的衔接,同时需要注意两点,①此处,subRL一定不是空节点了,这里与上面的情况不同了,这里是一个弯下来的状态,所以这个subRL这个节点是一定包含在里面的,所以不要判它为是否为空 ②同样的,这里所谓的parent可能还有根节点,所以还要对根节点的情况进行判断
这些做完之后就要改变相应的平衡因子,单旋的平衡因子是固定的,通过画图就可以得知。
旋转之后,AVL树就达到了平衡,就不需要再去调节了。
具体代码:
void RotateRL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf; // 需要提前记录,后面旋转之后会变成0
RotateR(subR);
RotateL(parent);
if (bf == 1)
{
parent->_bf = -1;
subR->_bf = 0;
subRL->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 0;
subR->_bf = 1;
subRL->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = 0;
subR->_bf = 0;
subRL->_bf = 0;
}
else
{
assert(false);
}
}
最后就是一些测试用到的一些代码
测试运用的代码
int _Size(Node* root)
{
return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;
}
int _Height(Node* root)
{
if (root == nullptr)
return 0;
return max(_Height(root->_left) , _Height(root->_right)) + 1;
}
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_kv.first << ":" << root->_kv.second << endl;
_InOrder(root->_right);
}
bool _IsBalance(Node* root)
{
if (root == nullptr)
{
return true;
}
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
if (abs(leftHeight - rightHeight) >= 2)
{
return false;
}
// 对平衡因子进行检查
if (rightHeight - leftHeight != root->_bf) //前面判断了高度,但这里还是建议对平衡因子进行检查,双保险
{
return false;
}
return _IsBalance(root->_left)
&& _IsBalance(root->_right);
}
};