本篇文章进行数据结构中AVL树的学习!!!
AVL树的由来:
历史:
一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
注意:
template <typename K, typename V>
struct AVLTNode
{
AVLTNode(const pair<K, V>& _kv)
: kv(_kv)
, left(nullptr)
, right(nullptr)
, parent(nullptr)
, _bf(0)
{}
pair<K, V> kv; // 存储节点值 -- first and second
AVLTNode<K, V>* left;
AVLTNode<K, V>* right;
AVLTNode<K, V>* parent;
int _bf; // 平衡因子,控制树的平衡
};
注意:
AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么AVL树的插入过程可以分为两步:
bool insert(const pair<K, V>& kv)
{
if (root == nullptr) {
root = new Node(kv);
root->_bf = 0;
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;
}
}
Node* newNode = new Node(kv);
cur = newNode; // 更新cur,待链接父节点
if (parent->kv.first > kv.first) {
parent->left = newNode;
}
else {
parent->right = newNode;
}
cur->parent = parent;
}
注意:按二叉搜索树规则插入,AVLTree为三叉链,链接新节点需要更新双亲节点
while (parent) // 一直向上回溯调整到祖先结束
{
// 如果插入节点在左边,则双亲节点平衡因子减1,右边加1
if (cur == parent->left) {
--parent->_bf;
}
else {
++parent->_bf;
}
// 如果双亲节点平衡因子为0,则不需要调整 -- 1 or -1 > 0 -- 插入节点填补了矮的节点(插入左边,且右边有节点)
if (parent->_bf == 0) {
break;
}
// 如果父节点平衡因子为1/-1,则继续调整 -- 0 > 1 or -1插入节点导致双亲节点变更或变矮(插入左边,且右边无节点)
else if (parent->_bf == 1 || parent->_bf == -1)
{
cur = cur->parent;
parent = parent->parent;
}
// 后面讲旋转会处理
else if (parent->_bf == 2 || parent->_bf == -2)
{}
}
注意:
如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种:
重点:展开抽象图的几种情况
// 左单旋 -- 新节点插入较高右子树的右侧 -- 右右
void RotateL(Node* parent)
{
Node* subR = parent->right;
Node* subRL = subR->left;
parent->right = subRL;
if (subRL != nullptr) {
subRL->parent = parent; // 如果左节点为空,则会造成空指针访问
}
Node* PpNode = parent->parent; // 后面parent不为根节点时,需要保存parent01的双亲节点进行链接
subR->left = parent;
parent->parent = subR;
// 一开始根节点可能是parent,旋转后要进行处理 -- 根应改为subR,根的双亲节点为nullptr
if (parent == root) {
root = subR;
root->parent = nullptr;
}
else {
// parent可能为整颗树的局部子树 -- 旋转后树的结构就乱了,需要将parent的双亲节点指向正确的位置
if (parent == PpNode->left) {
PpNode->left = subR;
}
else {
PpNode->right = subR;
}
subR->parent = PpNode;
}
// 更新平衡因子
parent->_bf = 0;
subR->_bf = 0;
}
注意:
// 右单旋 -- 新节点插入较高左子树的左侧 -- 左左
void RotateR(Node* parent)
{
// 旋转方法与左单旋一致
Node* subL = parent->left;
Node* subLR = subL->right;
parent->left = subLR;
if(subLR != nullptr)
subLR->parent = parent;
Node* PpNode = parent->parent;
subL->right = parent;
parent->parent = subL;
if (parent == root)
{
root = subL;
root->parent = nullptr;
}
else
{
if (PpNode->left == parent) {
PpNode->left = subL;
}
else {
PpNode->right = subL;
}
subL->parent = PpNode;
}
parent->_bf = 0;
subL->_bf = 0;
}
注意:右旋与左旋差不多这里就不画图了,参考左旋
// 左右双旋 -- 左子树高,左子树的右子树矮 subL: 1 subLR: -1
void RotateLR(Node* parent)
{
// 记录平衡因子和节点,待更新
Node* subL = parent->left;
Node* subLR = subL->right;
int bf = subLR->_bf;
// 先subL为根进行旋转,然后通过parent为根进行右旋
RotateL(parent->left);
RotateR(parent);
// 分三种情况,画图分析
if (bf == 0) {
parent->_bf = 0;
subL->_bf = 0;
subLR->_bf = 0;
}
else 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 {
assert(false);
}
}
注意:
// 右左双旋 -- subR: 1 subRL: -1
void RotateRL(Node* parent)
{
// 记录平衡因子和节点,待更新
Node * subR = parent->right;
Node* subRL = subR->left;
int bf = subRL->_bf;
RotateR(parent->right);
RotateL(parent);
// 画图分析 -- 三种情况
if (bf == 0)
{
parent->_bf = 0;
subR->_bf = 0;
subRL->_bf = 0;
}
else 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 {
assert(false);
}
}
假如以parent为根的子树不平衡,即parent的平衡因子为2或者-2,分以下情况考虑:
旋转完成后,原parent为根的子树个高度降低,已经平衡,不需要再向上更新
因为AVL树也是二叉搜索树,可按照二叉搜索树的方式将节点删除,然后再更新平衡因子,只不
错与删除不同的时,删除节点后的平衡因子更新,最差情况下一直要调整到根节点的位置。
具体实现可参考《算法导论》或《数据结构-用面向对象方法与C++描述》殷人昆版