AVL树是一种自平衡二叉搜索树,得名于其发明者G.M. Adelson-Velsky和Evgenii Landis。在AVL树中,两个子树的高度差(平衡因子)最多为1,因此它保持了相对的平衡。这种平衡性质确保了基本操作如添加、删除和查找等的时间复杂度均为O(log n),其中n是节点数。
using namespace std;
template>
class AVLTree{
private:
struct Node{
......
};
Node* root;
size_t size;
Compare compare;
void rotateLeft(Node** node);
void rotateRight(Node** node);
Node** findByKey(Key key,std::stack &nodePathStack);
void maintainBalance(std::stack &nodePathStack);
public:
AVLTree(){
this->compare = Compare();
this->size = 0;
this->root = nullptr;
}
int insert(Key key, Value value);
int remove(Key key);
void traversal(int type);
};
节点名 | 类型 | 描述 |
---|---|---|
left | Node* | 左子节点指针 |
right | Node* | 右子节点指针 |
height | size_t | 树的高度 |
key | Key | 节点键值,具有唯一性和可排序性 |
value | Value | 节点内存储的值 |
struct Node{
Key key;
Value value;
size_t height;
struct Node* left;
struct Node* right;
Node(const Key &k, const Value &v)
: key(k), value(v), height(1), left(nullptr), right(nullptr) {}
// 更新树的高度
inline void updateHeight() noexcept{
this->height = 1;
if(this->left) this->height = std::max(this->height,this->left->height + 1);
if(this->right) this->height = std::max(this->height,this->right->height + 1);
}
// 计算平衡因子
inline int getBlance() const noexcept{
return ((this->left) ? (this->left->height) : 0) - ((this->right) ? (this->right->height) : 0);
}
};
旋转操作是用于保持树的平衡性的重要机制。旋转操作不会改变二叉查找树(BST)的性质,即左子节点的关键值小于父节点,右子节点的关键值大于父节点。旋转操作分为两种:左旋(rotateLeft)和右旋(rotateRight)。它们主要用于调整树结构以维持AVL树的平衡。
左旋操作是以当前点为旋转点,以其右孩子作为旋转中心,逆时针旋转替代旋转中心的左孩子,此时旋转中心的左孩子被占,而旋转点右孩子空缺,因此将旋转中心的原左孩子变成旋转点的右孩子,最后将旋转中心提升到旋转点的位置。
由于AVL树的节点没有指向父亲的成员变量,为避免旋转过程中需要传递旋转点的父亲作为参数的一部分,需要采用二级指针来间接修改父亲指向旋转点的成员变量
旋转过后,旋转点和旋转中心的高度可能会发生变化,需要依次进行更新
/**
* @brief 左旋操作
* @param node 旋转点
* @return void
*/
template
void AVLTree::rotateLeft(Node** node) {
// | |
// L l-rotate(L) R
// / \ ==========> / \
// T1 R L T3
// / \ / \
// T2 T3 T1 T2
Node* p = *node;
Node *temp = (*node)->right;
// 需要修改三对关系
// 第一对
p->right = temp->left;
// 第二对
temp->left = p;
// 第三对
(*node) = temp;
// 更新的顺序不能调换
p->updateHeight();
temp->updateHeight();
}
右旋操作是以当前点为旋转点,以其左孩子作为旋转中心,顺时针旋转替代旋转中心的右孩子,此时旋转中心的右孩子被占,而旋转点左孩子空缺,因此将旋转中心的原右孩子变成旋转点的左孩子,最后将旋转中心提升到旋转点的位置
由于AVL树的节点没有指向父亲的成员变量,为避免旋转过程中需要传递旋转点的父亲作为参数的一部分,需要采用二级指针来间接修改父亲指向旋转点的成员变量
旋转过后,旋转点和旋转中心的高度可能会发生变化,需要依次进行更新
/**
* @brief 右旋操作
* @param node 旋转点
* @return void
*/
template
void AVLTree::rotateRight(Node** node){
// | |
// R r-rotate(R) L
// / \ ==========> / \
// L T3 T1 R
// / \ / \
// T1 T2 T2 T3
Node *p = *node;
Node *temp = (*node)->left;
// 需要修改三对关系
// 第一对
p->left = temp->right;
// 第二对
temp->right = p;
// 第三对
(*node) = temp;
// 更新的顺序不能调换
p->updateHeight();
temp->updateHeight();
}
红黑树本身也是二叉搜索树(BST),可以直接按照二叉搜索树的搜索流程进行,由于查找节点也会在插入和删除时用到,并从而涉及到对查询节点的修改,因此返回的是查询节点的地址。
而查询路径是插入或删除后维护AVL树平衡的一个重要信息,因此传递一个栈保存查找节点的路径作为返回值的一部分
/**
* @brief 查找节点,返回结果的地址
* @param key 查询键值
* @param nodePathStack 保存查找节点的路径
* @return Node** 返回查询节点的地址
*/
template
typename AVLTree::Node** AVLTree::findByKey(Key key,std::stack &nodePathStack){
Node **cur = &this->root;
while (*cur){
if (key == (*cur)->key) break;
nodePathStack.push(cur);
cur = this->compare(key,(*cur)->key) ? &(*cur)->left : &(*cur)->right;
}
return cur;
}
首先检查要插入的节点是否已存在,若存在,则直接更新键值并返回,否则直接插入,插入节点后可能会破坏AVL树的平衡,因此需要对插入新节点后所有受到影响的节点(也就是插入节点的祖先们)进行维护
/**
* @brief 插入或更新节点
* @param key 新节点的key
* @param value 新节点的value
* @return int 0表示插入成功,1表示节点已存在,更新value
*/
template
int AVLTree::insert(Key key, Value value){
std::stack nodePathStack;
Node **node = this->findByKey(key,nodePathStack);
if(*node){
(*node)->value = value;
return 1;
}
(*node) = new Node(key,value);
this->size++;
this->maintainBalance(nodePathStack);
return 0;
}
首先检查要删除的节点是否存在,若不存在,则删除失败,直接返回,若存在,则需要根据删除节点的孩子数量进行分析
case 1:被删除节点有2个孩子。则找到被删除节点的直接后继(或直接前驱),交换两个节点的键值对,并将要删除的节点变成直接后继(或直接前驱),由于直接后继(或直接前驱)至多有一个有一个孩子,因此将被删除节点有2个孩子的情形转换成了被删除节点有0个或1个孩子的情形。
同时由于被删除的节点发生了变化,需要继续更新保持了因为删除而受到影响的节点的栈
注:直接前驱指的是中序遍历中该节点的前一个元素,直接后继指的是中序遍历中该节点的后一个元素
case 2:被删除节点有0个或1个孩子,直接用孩子(没有即为nullptr)替换其位置
/**
* @brief 删除节点(若存在)
* @param key 需要删除节点的key
* @return int 0表示删除成功,1表示节点不存在,删除失败
*/
template
int AVLTree::remove(Key key){
std::stack nodePathStack;
Node **node = this->findByKey(key,nodePathStack);
Node *temp = *node;
if(!(*node)) return 1;
if((*node)->left && (*node)->right){
nodePathStack.push(node);
node = &(*node)->right;
while((*node)->left){
nodePathStack.push(node);
node = &(*node)->left;
}
temp->key = (*node)->key;
temp->value = (*node)->value;
}
temp = *node;
*node = (*node)->left ? (*node)->left : (*node)->right;
delete temp;
this->size--;
this->maintainBalance(nodePathStack);
return 0;
}
在插入或删除的过程中均设置了一个保存了因为插入或删除而受到影响的节点的栈,此时可以依次出栈获取节点
若节点的平衡因子<=1,则以当前节点为根的子树已平衡,跳过
若节点的平衡因子>1,则继续需要判断其为四种不平衡类型(LL、LR、 RL、 RR)的哪一种。
/**
* @brief 在插入或删除节点后维护AVL树的平衡
* @param nodePathStack 因为插入或删除而受到影响的节点的地址
* @return void
*/
template
void AVLTree::maintainBalance(std::stack &nodePathStack){
while (!nodePathStack.empty()){
Node** node = nodePathStack.top();nodePathStack.pop();
(*node)->updateHeight();
int blance = (*node)->getBlance();
if (std::abs(blance) <= 1) continue;
Node **child = blance == 2 ? (&(*node)->left) : (&(*node)->right);
size_t type = blance == 2 ? 0 : 2;
type |= (*child)->getBlance() == 1 ? 0 : 1;
switch (type){
case 0:
// LL
rotateRight(node);
break;
case 1:
// LR
rotateLeft(child);
rotateRight(node);
break;
case 2:
// RL
rotateRight(child);
rotateLeft(node);
break;
case 3:
// RR
rotateLeft(node);
break;
}
}
}
OIwiki AVL树 https://oi-wiki.org/ds/avl/
B站 平衡二叉树(AVL树) https://www.bilibili.com/video/BV1tZ421q72h
#include
#include
#include
#include
#include
using namespace std;
template>
class AVLTree{
private:
struct Node{
Key key;
Value value;
size_t height;
struct Node* left;
struct Node* right;
Node(const Key &k, const Value &v)
: key(k), value(v), height(1), left(nullptr), right(nullptr) {}
// 更新树的高度
inline void updateHeight() noexcept{
this->height = 1;
if(this->left) this->height = std::max(this->height,this->left->height + 1);
if(this->right) this->height = std::max(this->height,this->right->height + 1);
}
// 计算平衡因子
inline int getBlance() const noexcept{
return ((this->left) ? (this->left->height) : 0) - ((this->right) ? (this->right->height) : 0);
}
};
Node* root;
size_t size;
Compare compare;
void rotateLeft(Node** node);
void rotateRight(Node** node);
Node** findByKey(Key key,std::stack &nodePathStack);
void maintainBalance(std::stack &nodePathStack);
public:
AVLTree(){
this->compare = Compare();
this->size = 0;
this->root = nullptr;
}
int insert(Key key, Value value);
int remove(Key key);
void traversal(int type);
};
/**
* @brief 左旋操作
* @param node 旋转点
* @return void
*/
template
void AVLTree::rotateLeft(Node** node) {
// | |
// L l-rotate(L) R
// / \ ==========> / \
// T1 R L T3
// / \ / \
// T2 T3 T1 T2
Node* p = *node;
Node *temp = (*node)->right;
// 需要修改三对关系
// 第一对
p->right = temp->left;
// 第二对
temp->left = p;
// 第三对
(*node) = temp;
// 更新的顺序不能调换
p->updateHeight();
temp->updateHeight();
}
/**
* @brief 右旋操作
* @param node 旋转点
* @return void
*/
template
void AVLTree::rotateRight(Node** node){
// | |
// R r-rotate(R) L
// / \ ==========> / \
// L T3 T1 R
// / \ / \
// T1 T2 T2 T3
Node *p = *node;
Node *temp = (*node)->left;
// 需要修改三对关系
// 第一对
p->left = temp->right;
// 第二对
temp->right = p;
// 第三对
(*node) = temp;
// 更新的顺序不能调换
p->updateHeight();
temp->updateHeight();
}
/**
* @brief 查找节点,返回结果的地址
* @param key 查询键值
* @param nodePathStack 保存查找节点的路径
* @return Node** 返回查询节点的地址
*/
template
typename AVLTree::Node** AVLTree::findByKey(Key key,std::stack &nodePathStack){
Node **cur = &this->root;
while (*cur){
if (key == (*cur)->key) break;
nodePathStack.push(cur);
cur = this->compare(key,(*cur)->key) ? &(*cur)->left : &(*cur)->right;
}
return cur;
}
/**
* @brief 插入或更新节点
* @param key 新节点的key
* @param value 新节点的value
* @return int 0表示插入成功,1表示节点已存在,更新value
*/
template
int AVLTree::insert(Key key, Value value){
std::stack nodePathStack;
Node **node = this->findByKey(key,nodePathStack);
if(*node){
(*node)->value = value;
return 1;
}
(*node) = new Node(key,value);
this->size++;
this->maintainBalance(nodePathStack);
return 0;
}
/**
* @brief 删除节点(若存在)
* @param key 需要删除节点的key
* @return int 0表示删除成功,1表示节点不存在,删除失败
*/
template
int AVLTree::remove(Key key){
std::stack nodePathStack;
Node **node = this->findByKey(key,nodePathStack);
Node *temp = *node;
if(!(*node)) return 1;
if((*node)->left && (*node)->right){
nodePathStack.push(node);
node = &(*node)->right;
while((*node)->left){
nodePathStack.push(node);
node = &(*node)->left;
}
temp->key = (*node)->key;
temp->value = (*node)->value;
}
temp = *node;
*node = (*node)->left ? (*node)->left : (*node)->right;
delete temp;
this->size--;
this->maintainBalance(nodePathStack);
return 0;
}
/**
* @brief 在插入或删除节点后维护AVL树的平衡
* @param nodePathStack 因为插入或删除而受到影响的节点的地址
* @return void
*/
template
void AVLTree::maintainBalance(std::stack &nodePathStack){
while (!nodePathStack.empty()){
Node** node = nodePathStack.top();nodePathStack.pop();
(*node)->updateHeight();
int blance = (*node)->getBlance();
if (std::abs(blance) <= 1) continue;
Node **child = blance == 2 ? (&(*node)->left) : (&(*node)->right);
size_t type = blance == 2 ? 0 : 2;
type |= (*child)->getBlance() == 1 ? 0 : 1;
switch (type){
case 0:
// LL
rotateRight(node);
break;
case 1:
// LR
rotateLeft(child);
rotateRight(node);
break;
case 2:
// RL
rotateRight(child);
rotateLeft(node);
break;
case 3:
// RR
rotateLeft(node);
break;
}
}
}
/**
* @brief 前序、中序、后序遍历
* @param type 遍历类型 1->前序 2->中序 3->后序
* @return void
*/
template
void AVLTree::traversal(int type){
if(this->root==nullptr) return ;
std::stack> st;
st.push({this->root,0});
while(!st.empty()){
std::pair& node = st.top();
node.second++;
if(node.second == type ) std::cout << node.first->key << " ";
switch(node.second){
case 1:
if(node.first->left) st.push({node.first->left,0});
break;
case 2:
if(node.first->right) st.push({node.first->right,0});
break;
case 3:
st.pop();
break;
}
}
std::cout << std::endl;
}