前言
随着C++学习了两个多月,进度也来到了map和set的模拟实现。
而其底层是用红黑树模拟实现的,但是红黑树的实现又借鉴了平衡二叉树。因此先来处理平衡二叉树的问题,最重要的也就是旋转问题。
一、旋转有几种情况?
1.左单旋
这两种情况都需要进行左单旋
不同的是第一种情况需要将根节点变化
2.右单旋
这两种情况都需要进行右单旋
不同的是第一种情况需要将跟根节点变换
3.左右双旋
在变换过程中只需注意新插入的结点是在b中插入还是c中插入
这个信息可以巧妙的通过记录60的平衡因子来获知
4.右左双旋
在变换过程中只需注意新插入的结点是在b中插入还是c中插入
这个信息可以巧妙的通过记录60的平衡因子来获知
二、具体实现
1.树节点的实现
template<class T>
struct TreeNode{
TreeNode(const T& data)
:_data(data),
_left(nullptr),
_right(nullptr),
_parent(nullptr)
{}
T _data;
TreeNode* _left;
TreeNode* _right;
TreeNode* _parent;
int _bf;
};
这里的data表示树节点存的数据
然后分别有左 右 父亲 三个指针
最后 _bf表示当前结点的平衡因子是多少(以此来控制旋转)
2.旋转
注意我们的旋转只在平衡因子为2或者-2的结点旋转
并且每一次旋转完成之后 可以认为此时树中的所有结点已经平衡。
1.左单旋
void RotateL(Node* parent)
{
Node* cur = parent->_right;
Node* cur_left = cur->_left;
Node* pparent = parent->_parent;
parent->_right = cur_left;
if (cur_left != nullptr)
cur_left->_parent = parent;
cur->_left = parent;
parent->_parent = cur;
if (pparent == nullptr)
{
_root = cur;
cur->_parent = nullptr;
}
else
{
if (pparent->_left == parent)
{
pparent->_left = cur;
}
else
{
pparent->_right = cur;
}
cur->_parent = pparent;
}
cur->_bf = 0;
parent->_bf = 0;
}
如果 pparent为空指针,即为第一种情况,需要将根节点=cur;
否则,则判断是左孩子还是有孩子,使其连接起来。
最后将这三个结点的平衡因子全赋为0。
2.右单旋
void RotateR(Node* parent)
{
Node* cur = parent->_left;
Node* cur_right = cur->_right;
Node* pparent = parent->_parent;
parent->_left = cur_right;
if (cur_right != nullptr)
cur_right->_parent = parent;
cur->_right = parent;
parent->_parent = cur;
if (pparent == nullptr)
{
_root = cur;
cur->_parent = nullptr;
}
else
{
if (pparent->_left ==parent)
{
pparent->_left = cur;
}
else
{
pparent->_right = cur;
}
cur->_parent = pparent;
}
cur->_bf = 0;
parent->_bf = 0;
}
和 左单旋大差不差,在此不过多赘述。
3.左右双旋
void RotateLR(Node* parent)
{
Node* cur = parent->_left;
Node* cur_right = cur->_right;
int bf = cur_right->_bf;
RotateL(cur);
RotateR(parent);
if (bf == 0)
{
parent->_bf = 0;
cur->_bf = 0;
cur_right->_bf = 0;
}
else if (bf == 1)
{
parent->_bf = 0;
cur->_bf = -1;
}
else if (bf == -1)
{
parent->_bf = 1;
cur->_bf = 0;
}
else
{
assert(false);
}
}
在此先直接调用左单旋和右单旋函数。
但是会出现一个问题,左单旋和右单旋函数会将平衡因子直接修改为0,这显然是错误的。
所以我们先记录下它的孩子结点,然后用bf记录cur_right结点的平衡因子,来判断是在60结点的左边还是右边插入的。
若 bf=0,表示cur_right即为插入的结点,平衡因子全部修改为0;
若bf=1,表示插入到了右边;
若bf=-1,表示插入到了左边;
最后修改parent,cur对应的平衡因子即可。
至于为什么这么修改,读者可以去画图试一试。
4.右左双旋
void RotateRL(Node* parent)
{
Node* cur = parent->_right;
Node* cur_left = cur->_left;
int bf = cur_left->_bf;
RotateR(parent->_right);
RotateL(parent);
if (bf == 0)
{
parent->_bf = 0;
cur->_bf = 0;
cur_left->_bf = 0;
}
else if (bf == 1)
{
parent->_bf = -1;
cur->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 0;
cur->_bf = 1;
}
else
{
assert(false);
}
}
和上述实现基本一样,在此不过多赘述。
三、总结
以上就是平衡二叉树旋转的实现。
完整代码存放在Git-ee上 平衡二叉树。
学习任重而道远,接下来学习红黑树的插入!