自平衡二叉搜索树(AVL树)的C++实现

实现自平衡二叉搜索树(AVL树)的C++指南

在这篇博客中,我们将深入探讨自平衡二叉搜索树,特别是AVL树(由Adelson-Velsky和Landis命名),以及如何使用C++实现它。AVL树是一种特别的二叉搜索树,它可以在插入和删除节点后自动保持平衡,确保任何操作的时间复杂度始终为O(log n)。

阅读前我希望你能花点时间看看的问题以及回答


1. 什么是AVL树?

答: AVL树是一种自平衡二叉搜索树。在这种树中,任何节点的两个子树的高度最多相差1。这种高度平衡保证了树的操作(如搜索、插入、删除)在最坏情况下都能保持对数时间复杂度。


2. 为啥AVL树会被叫做AVL树?

答: AVL树得名于其发明者G. M. Adelson-Velsky和Evgenii Landis,他们在1962年首次提出了这种数据结构。


3. 为什么AVL树要保持平衡?

答: AVL树保持平衡主要是为了避免二叉搜索树退化成链表的情况,这种情况下,大多数树操作的时间复杂度会从O(log n)恶化为O(n)。通过保持树的平衡,AVL树确保了最坏情况下的操作效率。


4. AVL树的平衡因子是什么?

答: 平衡因子是节点的左子树高度与右子树高度的差。平衡因子的可能值为-1,0,或1。如果任何节点的平衡因子超出这个范围,AVL树会通过旋转来重新平衡。


5. AVL树中的旋转是什么?

答: 旋转是AVL树重平衡过程中的基本操作,包括左旋和右旋。旋转帮助树恢复平衡因子的正常范围(-1, 0, 1),从而维持操作的效率。


6. 插入新节点后,AVL树如何自我调整?

答: 在AVL树中插入新节点后,可能会导致某些节点的平衡因子超出允许范围。此时,树会从插入点向上遍历至根节点,找到第一个平衡因子绝对值超过1的节点,然后根据子树的状态执行相应的旋转操作。


7. 删除节点后,AVL树如何维持平衡?

答: 当在AVL树中删除节点后,如果任何节点的平衡因子变得不在允许的范围内,AVL树会执行旋转操作来重新平衡。删除操作可能需要从删除点向上追溯到根节点,进行多次旋转以确保整个树的平衡。


8. AVL树的旋转操作具体包括哪些类型?

答: AVL树主要包括四种旋转操作:左旋、右旋、左-右旋和右-左旋。左旋和右旋是单一方向的基本旋转,而左-右旋和右-左旋是两步操作,先对子节点进行旋转,然后对当前节点进行旋转。


9. AVL树的搜索效率如何?

答: 由于AVL树是平衡的,其搜索效率总是保持在O(log n),其中n是树中节点的数量。这确保了即使在连续的插入和删除操作之后,搜索操作也能快速完成。


10. AVL树与其他类型的树(如红黑树)相比有何优势和劣势?

答: AVL树的主要优势在于它提供了更严格的平衡,因此对于那些读操作比写操作频繁的场景更加适用,可以提供更快的查找性能。不过,这种严格的平衡也意味着插入和删除操作可能需要更频繁的旋转,导致写操作比红黑树稍慢。红黑树提供了更多的灵活性,牺牲了一点平衡(允许树的高度是最小高度的两倍),以减少在更新操作中需要的旋转次数。


带着上面的问题,下面我们一起来看看关于AVL树的详解以及对应的C++代码的详细实现。

什么是AVL树?

AVL树是一种自平衡二叉搜索树。对于树中的每一个节点,其左子树和右子树的高度最多相差1。这种高度平衡保证了树的深度保持对数级别,从而使得搜索、插入和删除操作都非常高效。

AVL树的特性

  • 高度平衡:对于任何节点,其左右子树的高度差(平衡因子)必须是-1、0或1。
  • 旋转操作:通过左旋、右旋、左右旋和右左旋等操作来维护树的平衡。
  • 动态更新:在每次插入或删除节点后,AVL树会通过旋转来重新平衡自己。

C++实现

接下来,我们将展示如何用C++实现一个基本的AVL树,包括节点定义、旋转操作和插入功能。

节点定义

每个节点存储一个键值、高度信息以及指向其左子节点和右子节点的指针。

struct AVLNode {
    int key;
    int height;
    AVLNode *left;
    AVLNode *right;

    AVLNode(int k) : key(k), height(1), left(nullptr), right(nullptr) {}
};

辅助函数

我们需要一些辅助函数来获取节点的高度和更新节点的高度,以及计算节点的平衡因子。

int getHeight(AVLNode* N) {
    if (N == nullptr)
        return 0;
    return N->height;
}

int getBalanceFactor(AVLNode* N) {
    if (N == nullptr)
        return 0;
    return getHeight(N->left) - getHeight(N->right);
}

void updateHeight(AVLNode* N) {
    N->height = 1 + max(getHeight(N->left), getHeight(N->right));
}

旋转操作

实现右旋和左旋操作。

AVLNode* rightRotate(AVLNode* y) {
    AVLNode* x = y->left;
    AVLNode* T2 = x->right;

    // Perform rotation
    x->right = y;
    y->left = T2;

    // Update heights
    updateHeight(y);
    updateHeight(x);

    return x; // new root
}

AVLNode* leftRotate(AVLNode* x) {
    AVLNode* y = x->right;
    AVLNode* T2 = y->left;

    // Perform rotation
    y->left = x;
    x->right = T2;

    // Update heights
    updateHeight(x);
    updateHeight(y);

    return y; // new root
}

插入操作

插入新键并通过旋转操作保持平衡。

AVLNode* insert(AVLNode* node, int key) {
    // 1. Perform the normal BST insertion
    if (node == nullptr)
        return new AVLNode(key);

    if (key < node->key)
        node->left = insert(node->left, key);
    else if (key > node->key)
        node->right = insert(node->right, key);
    else
        return node;

    // 2. Update height of this ancestor node
    updateHeight(node);

    // 3. Get the balance factor
    int balance = getBalanceFactor(node);

    // 4. If unbalanced, then there are 4 cases

    // Left Left Case
    if (balance > 1 && key < node->left->key)
        return rightRotate(node);

    // RightRight Case
    if (balance < -1 && key > node->right->key)
        return leftRotate(node);

    // Left Right Case
    if (balance > 1 && key > node->left->key) {
        node->left = leftRotate(node->left);
        return rightRotate(node);
    }

    // Right Left Case
    if (balance < -1 && key < node->right->key) {
        node->right = rightRotate(node->right);
        return leftRotate(node);
    }

    // return the (unchanged) node pointer
    return node;
}

AVL树的删除操作和其他功能

在AVL树中,删除操作是相对复杂的部分,因为在删除节点后,树可能会失去平衡。我们需要在删除节点后通过旋转操作重新平衡树。除了删除操作,我们还将实现树的遍历和搜索功能。

删除操作

删除节点需要处理三种情况:删除的节点是叶子节点,只有一个孩子的节点,或有两个孩子的节点。对于有两个孩子的节点,我们通常找到其后继节点(右子树中的最小节点)来替代它,然后删除原节点。

AVLNode* minValueNode(AVLNode* node) {
    AVLNode* current = node;
    while (current->left != nullptr)
        current = current->left;
    return current;
}

AVLNode* deleteNode(AVLNode* root, int key) {
    // STEP 1: Perform standard BST delete
    if (root == nullptr)
        return root;

    if (key < root->key)
        root->left = deleteNode(root->left, key);
    else if (key > root->key)
        root->right = deleteNode(root->right, key);
    else {
        if ((root->left == nullptr) || (root->right == nullptr)) {
            AVLNode* temp = root->left ? root->left : root->right;
            if (temp == nullptr) {
                temp = root;
                root = nullptr;
            } else {
                *root = *temp;
            }
            delete temp;
        } else {
            AVLNode* temp = minValueNode(root->right);
            root->key = temp->key;
            root->right = deleteNode(root->right, temp->key);
        }
    }

    if (root == nullptr)
      return root;

    // STEP 2: Update the height of the current node
    updateHeight(root);

    // STEP 3: Get the balance factor
    int balance = getBalanceFactor(root);

    // STEP 4: Balance the tree
    if (balance > 1 && getBalanceFactor(root->left) >= 0)
        return rightRotate(root);

    if (balance > 1 && getBalanceFactor(root->left) < 0) {
        root->left = leftRotate(root->left);
        return rightRotate(root);
    }

    if (balance < -1 && getBalanceFactor(root->right) <= 0)
        return leftRotate(root);

    if (balance < -1 && getBalanceFactor(root->right) > 0) {
        root->right = rightRotate(root->right);
        return leftRotate(root);
    }

    return root;
}
其他功能:遍历和搜索

遍历树通常有三种方式:前序、中序和后序。这里我们实现中序遍历,因为它可以按排序顺序输出树中的元素。

void inOrder(AVLNode* root) {
    if (root != nullptr) {
        inOrder(root->left);
        std::cout << root->key << " ";
        inOrder(root->right);
    }
}

搜索功能:

AVLNode* search(AVLNode* root, int key) {
    if (root == nullptr || root->key == key)
        return root;

    if (root->key < key)
        return search(root->right, key);

    return search(root->left, key);
}

完整的C++代码示例

现在让我们把前面提到的这些部分组合成一个完整的C++程序。

#include <iostream>
#include <algorithm>

// AVL树节点结构体
struct AVLNode {
    int key;      // 节点键值
    int height;   // 节点高度
    AVLNode *left;  // 左子节点
    AVLNode *right; // 右子节点

    // 构造函数,初始化键值、高度和子节点
    AVLNode(int k) : key(k), height(1), left(nullptr), right(nullptr) {}
};

// 获取节点的高度
int getHeight(AVLNode* N) {
    if (N == nullptr)
        return 0;
    return N->height;
}

// 计算节点的平衡因子
int getBalanceFactor(AVLNode* N) {
    if (N == nullptr)
        return 0;
    return getHeight(N->left) - getHeight(N->right);
}

// 更新节点的高度
void updateHeight(AVLNode* N) {
    if (N != nullptr)
        N->height = 1 + std::max(getHeight(N->left), getHeight(N->right));
}

// 右旋转操作
AVLNode* rightRotate(AVLNode* y) {
    AVLNode* x = y->left;
    AVLNode* T2 = x->right;
    x->right =y;
    y->left = T2;

    updateHeight(y);
    updateHeight(x);

    return x;
}

// 左旋转操作
AVLNode* leftRotate(AVLNode* x) {
    AVLNode* y = x->right;
    AVLNode* T2 = y->left;
    y->left = x;
    x->right = T2;

    updateHeight(x);
    updateHeight(y);

    return y;
}

// 插入节点
AVLNode* insert(AVLNode* node, int key) {
    // 如果是空节点,创建新节点
    if (node == nullptr)
        return new AVLNode(key);

    // 根据键值递归插入左子树或右子树
    if (key < node->key)
        node->left = insert(node->left, key);
    else if (key > node->key)
        node->right = insert(node->right, key);
    else
        return node;

    // 更新节点高度
    updateHeight(node);

    // 获取节点的平衡因子
    int balance = getBalanceFactor(node);

    // 根据平衡因子进行旋转调整
    if (balance > 1 && key < node->left->key)
        return rightRotate(node);

    if (balance < -1 && key > node->right->key)
        return leftRotate(node);

    if (balance > 1 && key > node->left->key) {
        node->left = leftRotate(node->left);
        return rightRotate(node);
    }

    if (balance < -1 && key < node->right->key) {
        node->right = rightRotate(node->right);
        return leftRotate(node);
    }

    return node;
}

// 中序遍历
void inOrder(AVLNode* root) {
    if (root != nullptr) {
        inOrder(root->left);
        std::cout << root->key << " ";
        inOrder(root->right);
    }
}

// 找到给定节点的最小值节点
AVLNode* minValueNode(AVLNode* node) {
    AVLNode* current = node;
    while (current->left != nullptr)
        current = current->left;
    return current;
}

// 删除节点
AVLNode* deleteNode(AVLNode* root, int key) {
    // 如果树为空,返回原树
    if (root == nullptr)
        return root;

    // 递归地在左子树或右子树中删除节点
    if (key < root->key)
        root->left = deleteNode(root->left, key);
    else if (key > root->key)
        root->right = deleteNode(root->right, key);
    else {
        // 找到要删除的节点
        if ((root->left == nullptr) || (root->right == nullptr)) {
            AVLNode* temp = root->left ? root->left : root->right;
            if (temp == nullptr) {
                temp = root;
                root = nullptr;
            } else {
                *root = *temp;
            }
            delete temp;
        } else {
            AVLNode* temp = minValueNode(root->right);
            root->key = temp->key;
            root->right = deleteNode(root->right, temp->key);
        }
    }

    // 如果树为空,返回空树
    if (root == nullptr)
      return root;

    // 更新节点高度
    updateHeight(root);

    // 获取节点的平衡因子
    int balance = getBalanceFactor(root);

    // 根据平衡因子进行旋转调整
    if (balance > 1 && getBalanceFactor(root->left) >= 0)
        return rightRotate(root);

    if (balance > 1 && getBalanceFactor(root->left) < 0) {
        root->left = leftRotate(root->left);
        return rightRotate(root);
    }

    if (balance < -1 && getBalanceFactor(root->right) <= 0)
        return leftRotate(root);

    if (balance < -1 && getBalanceFactor(root->right) > 0) {
        root->right = rightRotate(root->right);
        return leftRotate(root);
    }

    return root;
}

int main() {
    AVLNode* root = nullptr;
    // 插入节点
    root = insert(root, 10);
    root = insert(root, 20);
    root = insert(root, 30);
    root = insert(root, 40);
    root = insert(root, 50);
    root = insert(root, 25);

    // 中序遍历 AVL 树
    std::cout << "Inorder traversal of the constructed AVL tree is \n";
    inOrder(root);
    std::cout << std::endl;

    // 删除节点 20
    root = deleteNode(root, 20);

    // 再次中序遍历 AVL 树
    std::cout << "Inorder traversal after deletion of 20 \n";
    inOrder(root);
    std::cout << std::endl;

    return 0;
}

在这个demo中,我们首先创建了一个AVL树并插入了几个键值,然后执行了一个删除操作,并且显示了删除前后的中序遍历的结果。该demo主要演示如何在保持AVL树平衡的同时添加和删除节点,对于一些其它更加复杂的操作大家可以在理解了AVL树的基本原理的基础上进行添加。记住一句话,万变不离其宗,基础决定伟大。

Conclusion

AVL树通过在每次插入和删除操作后维持高度平衡,确保了操作的高效性。使用C++自己实现AVL树不仅可以加深我们对动态数据结构的理解,还能切实提高解决实际问题的能力。虽然标准库中已提供了如 std::setstd::mapstd::multisetstd::multimap 等基于红黑树的平衡搜索树实现,但深入理解和实现AVL树对于学习和掌握高级数据结构还是对我们非常有帮助。

希望这篇博客能帮助你了解和实现自己的AVL树。练习实现这种数据结构是一个非常好的学习过程,能够帮助理解树结构的动态平衡机制。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值