AVL-树
AVL:
二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家 G.M.Adelson-Velskii 和 E.M.Landis 在 1962 年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过 1 (需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
一棵 AVL 树或者是空树,或者是具有以下性质的二叉搜索树:
- 它的左右子树都是 AVL 树
- 左右子树高度之差 (简称平衡因子) 的绝对值不超过 1 (-1/0/1)
AVL 树的性能
AVL 树是绝对平衡的二叉搜索树,要求每个节点左右子树高度差绝对值不超 1,能保证查询时高效的时间复杂度,即log2(N) 。不过对其做结构修改操作性能低下,插入时为维护平衡旋转次数多,删除时甚至可能让旋转持续到根节点。因此,若需查询高效且有序,数据个数静态(不会改变)的数据结构,可考虑 AVL 树;若结构经常修改,则不太适合 。
AVL 树适用于对数据查找、插入和删除操作性能要求较高,且数据动态变化的场景。以下是几个实用的例子:
- 数据库索引:数据库系统经常需要快速查找、插入和更新数据。例如,在一个存储用户信息的数据库表中,以用户 ID 作为关键字建立 AVL 树索引。当查询某个特定用户的信息时,能通过 AVL 树快速定位到对应的记录,时间复杂度为O(logn)。而且,当有新用户注册或用户信息发生变更时,AVL 树可以高效地进行插入和更新操作,并通过自动调整保持平衡,确保索引的性能稳定。
- 编译器符号表管理:在编译器中,符号表用于存储程序中定义的变量、函数等符号的信息。在编译过程中,需要频繁地查找符号以检查其是否已定义、获取其相关属性,同时在声明新符号时要插入到符号表中。使用 AVL 树来管理符号表,可以快速实现这些操作。例如,当编译一段包含大量变量定义和引用的代码时,AVL 树能够高效地处理符号的查找和插入,帮助编译器快速完成语法分析和语义检查等工作。
- 内存管理系统:在操作系统的内存管理中,需要跟踪内存块的分配和释放情况。可以使用 AVL 树来维护一个空闲内存块的列表,以内存块的地址或大小作为关键字。当进程请求内存时,能快速在 AVL 树中找到合适的空闲内存块进行分配;当进程释放内存时,又能将释放的内存块插入到 AVL 树中,并通过调整保持树的平衡。这样可以有效地提高内存分配和回收的效率,避免内存碎片的产生。
- 搜索引擎:在搜索引擎的索引结构中,AVL 树可以用于存储单词到文档的映射关系。例如,对于每个单词,将包含该单词的文档 ID 作为节点值构建 AVL 树。当用户输入查询关键词时,搜索引擎可以迅速在 AVL 树中查找相关单词,并获取包含该单词的文档列表,实现快速的信息检索。同时,当有新文档被索引或者文档中的单词发生变化时,AVL 树能够方便地进行插入和更新操作,保证索引的准确性和时效性。
完整代码:
#include<iostream>
using namespace std;
#include<assert.h>
template<class K,class V>
struct AVLnode
{
AVLnode<K, V>* _left;
AVLnode<K, V>* _right;
AVLnode<K, V>* _parent;
pair<K, V> _kv;
int _bf;
AVLnode(const pair<K,V> &kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr){}
};
template<class K,class V>
class AVLtree
{
typedef AVLnode<K, V> node;
public:
bool insert(pair<K, V> kv)
{
if (_root == nullptr)
{
node* newnode = new node(kv);
return true;
}
node* cur = _root;
node* parent = nullptr;
while (cur)
{
parent = cur;
if (cur->_kv.first < kv.first)
{
cur = cur->_right;
}
else if (cur->_kv.first > kv.first)
{
cur = cur->_left;
}
else
{
return false;
}
}
cur = new node(kv);
(parent->_kv.first > kv.first) ? (parent->_left = cur) : (parent->_right = cur);
//不同于二叉搜索树的是avl是相当于双向的,所以在这个完成后还要进行一个反向的指向
cur->_parent = parent;
//关于这里面用parent是因为平衡因子的调整是向上调整的,
//当parent走到的头结点,头结点再往上没有parent了直接为空此时就算是调整完成
while (parent)
{
if (cur == parent->_left)
{
parent->_bf--;
}
else
{
parent->_bf++;
}
if (parent->_bf == 0)
{
break;
}
else if (parent->_bf == 1 || parent->_bf == -1)
{
cur = parent;
parent = parent->_parent;
}
else//这个else就要涉及到旋转调整问题!
{
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)
{
RotateRL(parent);
}
else if (parent->_bf == -2 && cur->_bf == 1)
{
RotateLR(parent);
}
break;
}
}
return true;
}
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 find(const K& key)
{
if (_root == nullptr)
{
return false;
}
node* cur = _root;
while (cur)
{
if(cur->_kv.first>key)
{
cur = cur->_left;
}
else if (cur->_kv.first < key)
{
cur = cur->_right;
}
else
{
return true;
}
}
return false;
}
bool IsBalance()
{
return _IsBalance(_root);
}
void inorder()
{
cout << "中序遍历:" << " ";
_inorder(_root);
}
size_t size()
{
return _size(_root);
}
private:
size_t _size(node* root)
{
if (root == nullptr)
return nullptr;
return _size(root->_left) + _size(root->_right) + 1;
}
void _inorder(node* root)
{
if (root == nullptr)
return;
_inorder(root->_left);
cout << "first: " << root->_kv.first << " : " << root->_kv.second << " ";
_inorder(root->_right);
}
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 << root->_kv.firs << "平衡因子异常" << endl;
return false;
}
return abs(rightHeight - leftHeight) < 2&&_IsBalance(root->_left)&&_IsBalance(root->_right);
}
void RotateL(node* parent)
{
node* p_parent = parent->_parent;
node* subR = parent->_right;
node* subRL = subR->_right;
parent->_right = subRL;
subR->_left = parent;
parent->_parent = subR;
if (subRL)
subRL->_parent = parent;
if (_root == parent)
{
_root = subR;
subR->_parent = nullptr;
}
else
{
if (p_parent->_left == parent)//记住因为此时原树并没有被摧毁,parent节点也还没有更新,并不是说树的指针只能有两个指向
{
p_parent->_left = subR;
}
else
{
p_parent->_right = subR;
}
subR->_parent = p_parent;
}
parent->_bf = subR->_bf = 0;
}
void RotateR(node* parent)
{
node* subL = parent->_left;
node* subLR = subL->_right;
node* p_parent = parent->_parent;
subL->_right = parent;
parent->_left = subLR;
if (subLR)
{
subLR->_parent = parent;
}
if (_root == parent)
{
_root = subL;
subL->_parent = nullptr;
}
else
{
if (p_parent->_left == parent)
{
p_parent->_left = subL;
}
else if (p_parent->_right == parent)
{
p_parent->_right = subL;
}
subL->_parent = p_parent;
}
parent->_bf = subL->_bf = 0;
}
// 左右旋操作
void RotateLR(node* parent) {
node* subL = parent->_left;
node* subLR = subL->_right;
int bf = subLR->_bf;
RotateL(parent->_left);
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 {
parent->_bf = 0;
subL->_bf = 0;
subLR->_bf = 0;
}
}
// 右左旋操作
void RotateRL(node* parent)
{
node* subR = parent->_right;
node* subRL = subR->_left;
int bf = subRL->_bf;
RotateR(parent->_right);
RotateL(parent);
if (bf == 1)
{
subRL->_bf = 0;
parent->_bf = -1;
subR->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 0;
subR->_bf = 1;
subRL->_bf = 0;
}
else
{
parent->_bf = 0;
subR->_bf = 0;
subRL->_bf = 0;
}
}
node* _root;
};