数据结构——AVL树

定义

AVL树是一种自平衡二叉搜索树,得名于其发明者G.M. Adelson-Velsky和Evgenii Landis。在AVL树中,两个子树的高度差(平衡因子)最多为1,因此它保持了相对的平衡。这种平衡性质确保了基本操作如添加、删除和查找等的时间复杂度均为O(log n),其中n是节点数。

结构

AVL树
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树的插入

首先检查要插入的节点是否已存在,若存在,则直接更新键值并返回,否则直接插入,插入节点后可能会破坏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;
}

AVL树的删除

首先检查要删除的节点是否存在,若不存在,则删除失败,直接返回,若存在,则需要根据删除节点的孩子数量进行分析

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)的哪一种。

  1. 首先看平衡因子,若平衡因子为2,则说明是由于左子树过高而引起的不平衡,不平衡类型第一位确定为L,反之若为-2,则是右子树过高而引起的不平衡,不平衡类型第一位确定为R
  2. 接着继续看过高子树的根节点的平衡因子,若为1,则确定不平衡类型第二位确定为L;若为0,则不平衡类型第二位为L或R均可;若为-1,则确定不平衡类型第二位R
  3. 根据不平衡类型进行旋转
    • 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;
}

你可能感兴趣的:(数据结构,算法)