🏆个人主页:企鹅不叫的博客
? 🌈专栏
?? 博主码云gitee链接:代码仓库地址
?若有帮助可以【关注+点赞+收藏】,大家一起进步!
💙系列文章💙
【初阶与进阶C++详解】第一篇:C++入门知识必备
【初阶与进阶C++详解】第二篇:C&&C++互相调用(创建静态库)并保护加密源文件
【初阶与进阶C++详解】第三篇:类和对象上(类和this指针)
【初阶与进阶C++详解】第四篇:类和对象中(类的六个默认成员函数)
【初阶与进阶C++详解】第五篇:类和对象下(构造+static+友元+内部类
【初阶与进阶C++详解】第六篇:C&C++内存管理(动态内存分布+内存管理+new&delete)
【初阶与进阶C++详解】第七篇:模板初阶(泛型编程+函数模板+类模板+模板特化+模板分离编译)
【初阶与进阶C++详解】第八篇:string类(标准库string类+string类模拟实现)
【初阶与进阶C++详解】第九篇:vector(vector接口介绍+vector模拟实现+vector迭代器区间构造/拷贝构造/赋值)
【初阶与进阶C++详解】第十篇:list(list接口介绍和使用+list模拟实现+反向迭代器和迭代器适配)
【初阶与进阶C++详解】第十一篇:stack+queue+priority_queue+deque(仿函数)
【初阶与进阶C++详解】第十二篇:模板进阶(函数模板特化+类模板特化+模板分离编译)
【初阶与进阶C++详解】第十三篇:继承(菱形继承+菱形虚拟继承+组合)
【初阶与进阶C++详解】第十四篇:多态(虚函数+重写(覆盖)+抽象类+单继承和多继承)
【初阶与进阶C++详解】第十五篇:二叉树搜索树(操作+实现+应用KVL+性能+习题)
💎一、AVL树概念
二叉搜索树虽可以缩短查找的效率(logN),但如果数据有序或接近有序二叉搜索树将退化为单支树(On),查找元素相当于在顺序表中搜索元素,效率低下。
平衡树也是搜索二叉树,其引入了一个平衡因子的概念,用于控制搜索二叉树的平衡。它会保证左右子树的高度之差(绝对值)不超过1。当新插入节点导致高度之差超过1时,便会触发旋转,使得树的高度降低,稳定了二叉搜索树效率。
-
它的左右子树都是AVL树 -
左右子树高度之差的绝对值(也叫平衡因子)不超过1 -
平衡因子= 右子树高度 - 左子树高度
💎二、AVL树节点的定义
这里节点是一个三叉链,里面存放了元素的数据和该节点此时的平衡因子。
- 左右子树高度相同 0
- 左子树高于右子树 -1
- 右子树高于左子树 1
template<class K,class V>
struct AVLTreeNode
{
pair<K, V> _kv;
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent;
int _bf;
AVLTreeNode(const pair<K, V>& kv)
:_kv(kv),
_left(nullptr),
_right(nullptr),
_parent(nullptr),
_bf(0)
{}
};
💎三、AVL树插入
🏆1.平衡因子的调节
AVL需要控制高度,所以每次插入我们都需要更新判断树节点的平衡因子是否符合要求
思路:
-
如果是空树,给root new一个新节点 -
根据二叉搜索树的规则(左小右大规则)来找到新节点应该插入的位置,进行插入 -
插入之后,需要向上更新平衡因子(可能包括多个祖先),如果该插入节点在父节点的右边,平衡因子+1,如果在该节点的左边,平衡因子-1。 -
parent更新后的平衡因子为1或-1,则说明在parent的右边或者左边新增了结点,从而影响了parent的父亲结点所以要继续往上更新平衡因子。 -
parent更新后的平衡因子为0,说明插入前父亲的平衡因子为1或-1经过++或- -变成0的,说明新增结点在parent矮的一侧使得parent的左右子树一样高,不会影响parent的父亲结点,就不用往上更新平衡因子。 -
如果平衡因子的绝对值等于2,已经不满足AVL树,说明当前就需要旋转
框架代码:
bool Insert(const pair<K, V>& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
_root->_bf = 0;
return true;
}
Node* parent = nullptr;
Node* cur = _root;
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);
if (parent->_kv.first < kv.first)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
cur->_parent = parent;
while (parent)
{
if (cur == parent->_right)
{
parent->_bf++;
}
else
{
parent->_bf--;
}
if (parent->_bf == 0)
{
break;
}
else if (parent->_bf == 1 || parent->_bf == -1)
{
cur = cur->_parent;
parent = parent->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -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);
}
break;
}
else
{
assert(false);
}
}
return true;
}
💎四、AVL树旋转(重点)
1.1左单旋(新插入的节点在右子树的右侧)
步骤:让subR的左孩子成为parent的右孩子,然后让parent成为subR的左孩子,最后把两个节点的平衡因子修改为0。
代码部分:
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
Node* ppNode = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
if (parent == _root)
{
_root = subR;
_root->_parent = nullptr;
}
else
{
if (parent == ppNode->_left)
{
ppNode->_left = subR;
}
else
{
ppNode->_right = subR;
}
subR->_parent = ppNode;
}
subR->_bf = parent->_bf = 0;
}
1.2右单旋(新节点插入到较高左子树的左侧)
步骤: 让subL的右孩子成为parent的左孩子,然后让parent成为subL的右孩子,最后把两个节点的平衡因子修改为0
代码部分:
void RotateR(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 (parent == _root)
{
_root = subL;
_root->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subL;
}
else
{
ppNode->_right = subL;
}
subL->_parent = ppNode;
}
subL->_bf = parent->_bf = 0;
}
两种单旋的情况:
? 左单旋,prev平衡因子为2,subR平衡因子为1,需要左单旋
右单旋,prev平衡因子为-2,subL平衡因子为-1,需要右单旋
总结:当父节点的平衡因子的绝对值超过1,其左/右边节点的平衡因子为1且和父节点平衡因子的正负相同时,需要向另外一个方向进行单旋
else if (parent->_bf == 2 || parent->_bf == -2)
{
if (parent->_bf == 2 && cur->_bf == 1)
{
RotateL(parent);
}
else if (parent->_bf == -2 && cur->_bf == -1)
{
RotateR(parent);
}
else
{
}
break;
}
1.3右左双旋(新节点插入在较高右子树左侧,这里和第一种情况的区别就是前者是直线,后者是折线)
步骤:先对subR进行一个右单旋,然后对parent进行左单旋,修改平衡因子
如果subRL的平衡因子为0,就将它们依次改为0,0, 0;本身就是新增节点 如果subRL的平衡因子为1,就将它们依次改为-1,0, 0;
如果subRL的平衡因子为-1,就将它们依次改为0,0, 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)
{
subRL->_bf = 0;
parent->_bf = 0;
subR->_bf = 0;
}
else if (bf == 1)
{
subRL->_bf = 0;
parent->_bf = -1;
subR->_bf = 0;
}
else if (bf == -1)
{
subRL->_bf = 0;
parent->_bf = 0;
subR->_bf = 1;
}
else
{
assert(false);
}
}
1.4左右双旋(新节点插入在较高右子树左侧,这里和第一种情况的区别就是前者是直线,后者是折线)
步骤:先对subL进行一个左单旋,然后对parent进行右单旋,修改平衡因子
如果subLR的平衡因子为0,就将它们依次改为0,0, 0; 如果subLR的平衡因子为1,就将它们依次改为-1,0, 0;
如果subLR的平衡因子为-1,就将它们依次改为0,0, 1
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
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);
}
}
💎五、AVL树验证
AVL树有两点需要验证,通过高度判断,子树两边高度是否平衡,根据二叉搜索树的特性,判断平衡因子是否符合
int _Height(Node* root)
{
if (root == nullptr)
return 0;
int lh = _Height(root->_left);
int rh = _Height(root->_right);
return lh > rh ? lh + 1 : rh + 1;
}
bool _IsBalanceTree(Node* root)
{
if (nullptr == root)
return true;
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
int diff = rightHeight - leftHeight;
if (abs(diff) >= 2)
{
cout << root->_kv.first << "节点平衡因子异常" << endl;
return false;
}
if (diff != root->_bf)
{
cout << root->_kv.first << "节点平衡因子不符合实际" << endl;
return false;
}
return _IsBalanceTree(root->_left)
&& _IsBalanceTree(root->_right);
}
实例演示:
void TestAVLTree2()
{
const size_t N = 1024*1024;
vector<int> v;
v.reserve(N);
srand(time(0));
for (size_t i = 0; i < N; ++i)
{
v.push_back(rand());
}
AVLTree<int, int> t;
for (auto e : v)
{
t.Insert(make_pair(e, e));
}
cout << "是否平衡?" << t.IsBalanceTree() << endl;
cout << "高度:" << t.Height() << endl;
}
💎六、AVL树删除(了解)
思路:
1.按照二叉搜索树的规则删除
2.更新平衡因子,并且进行旋转来调整(最坏情况下可能会一直调整到根节点)。
bool Erase(const K& key)
{
if (_root == nullptr)
return false;
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (key < cur->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else if (key > cur->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else
{
if (cur->_left == nullptr)
{
if (cur == _root)
{
_root = cur->_right;
delete cur;
break;
}
else
{
if (parent->_left == cur)
{
parent->_left = cur->_right;
parent->_bf++;
}
else
{
parent->_right = cur->_right;
parent->_bf--;
}
}
if (parent->_bf != -1 && parent->_bf != 1) AfterEraseUpdateBf(parent);
delete cur;
}
else if (cur->_right == nullptr)
{
if (cur == _root)
{
_root = cur->_left;
delete cur;
break;
}
else
{
if (parent->_left == cur)
{
parent->_left = cur->_left;
parent->_bf++;
}
else
{
parent->_right = cur->_left;
parent->_bf--;
}
}
if (parent->_bf != -1 && parent->_bf != 1) AfterEraseUpdateBf(parent);
delete cur;
}
else
{
Node* rightMinParent = cur;
Node* rightMin = cur->_right;
while (rightMin->_left)
{
rightMinParent = rightMin;
rightMin = rightMin->_left;
}
cur->_kv = rightMin->_kv;
if (rightMinParent->_left == rightMin)
{
rightMinParent->_left = rightMin->_right;
rightMinParent->_bf++;
}
else
{
rightMinParent->_right = rightMin->_right;
rightMinParent->_bf--;
}
if (rightMinParent->_bf != -1 && rightMinParent->_bf != 1) AfterEraseUpdateBf(rightMinParent);
delete rightMin;
}
return true;
}
}
return false;
}
void AfterEraseUpdateBf(Node* parent)
{
if (parent == nullptr)
return;
Node* cur = parent;
goto first;
while (parent)
{
if (cur == parent->_left)
parent->_bf++;
else
parent->_bf--;
first:
if (parent->_bf == 0)
{
if (parent->_parent == nullptr)
break;
cur = parent;
parent = parent->_parent;
}
else if (parent->_bf == -1 || parent->_bf == 1)
{
break;
}
else
{
if (parent->_bf == 2)
{
if (parent->_right->_bf == 1)
{
RotateL(parent);
cur = parent->_parent;
parent = cur->_parent;
continue;
}
else if (parent->_right->_bf == -1)
{
Node* s = parent->_right;
Node* sL = s->_left;
RotateRL(parent);
{
cur = sL;
parent = cur->_parent;
continue;
}
}
else if (parent->_right->_bf == 0)
{
RotateL(parent);
parent->_bf = 1;
parent->_parent->_bf = -1;
}
}
else if (parent->_bf == -2)
{
if (parent->_left->_bf == -1)
{
RotateR(parent);
cur = parent->_parent;
parent = cur->_parent;
continue;
}
else if (parent->_left->_bf == 1)
{
Node* s = parent->_left;
Node* sR = s->_right;
RotateLR(parent);
{
cur = sR;
parent = cur->_parent;
continue;
}
}
else if (parent->_left->_bf == 0)
{
RotateR(parent);
parent->_parent->_bf = 1;
parent->_bf = -1;
}
}
break;
}
}
}
💎七、AVL树查找
和KVL树一样
Node* _FindR(Node* root, const K& key)
{
if (root == nullptr)
return nullptr;
if (root->_kv.first < key)
{
return _FindR(root->_right, key);
}
else if (root->_kv.first > key)
{
return _FindR(root->_left, key);
}
else
{
return root;
}
}
💎八、AVL树性能
- AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度logN
- 但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置
|