【基本数据结构】平衡二叉树

前言

本系列专注更新基本数据结构,现有以下文章:

【算法与数据结构】数组.

【算法与数据结构】链表.

【算法与数据结构】哈希表.

【算法与数据结构】二叉查找树

【算法与数据结构】平衡二叉树


平衡二叉树

1 简介

平衡二叉树是为了解决二叉查找树中树退化成链这一问题而提出的。平衡二叉树在插入、删除某一节点之后通过左旋或右旋操作保持动态平衡,从而避免节点失衡(最坏的情况是树退化成链)。

二叉树某节点是否失衡由该节点的 失衡因子 来衡量,失衡因子为左子树节点高度与右子节点高度之差。

当二叉树中某节点的左、右子树高度差不超过1,则表示该节点平衡,高度差不超过 1 对应平衡因子的值为 ±10

左旋与右旋,由于插入、删除节点导致二叉树中出现不平衡节点,此时可以通过左旋或右旋操作调整节点位置以达到二叉查找树的动态平衡。平衡二叉树也是一棵二叉查找树,它的查找、插入、删除都与二叉查找树的操作相同,只是在插入、删除新的节点之后需要通过一些旋转操作来保持其平衡性。

2 旋转

2.1 左旋

左旋,顾名思义,指的是向左旋转节点,或者是逆时针旋转节点。在图 2-1 中,由于插入了一个新的节点到平衡二叉树中,根节点 3 平衡因子变为 0-2=-2,说明此时根节点已经失衡了。为了保持这棵二叉查找树的平衡性,需要左旋根节点。

平衡二叉树-左旋.drawio (1)
图 2-1 左旋

实现代码

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

    y->left = x;
    x->right = T2;

    x->height = std::max(height(x->left), height(x->right)) + 1;
    y->height = std::max(height(y->left), height(y->right)) + 1;

    return y;
}

上述代码是左旋的具体操作,传入的一个节点指针 x,表示从该节点开始进行左旋,最终返回的是左旋操作后该子树新的根节点。

以图 2-1 为例,传入的节点为 3,首先记录 3 的右子节点 4,记作 y;接着记录 y 的左子节点空节点,记作 T2;接下来将 4 的左链接连到 3 上,3 的右链接连到空节点上。

最后,还需要更新节点 xy 的高度,为后面计算平衡因子做准备。

2.2 右旋

右旋,指的是向右旋转节点,或者是顺时针旋转节点。在图 2-2 中,由于插入了一个新的节点到平衡二叉树中,根节点 9 平衡因子变为 2-0=2,说明此时根节点已经失衡了。为了保持这棵二叉查找树的平衡性,需要右旋根节点。

平衡二叉树-右旋.drawio (1)
图 2-2 右旋

实现代码

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

    x->right = y;
    y->left = T2;

    y->height = std::max(height(y->left), height(y->right)) + 1;
    x->height = std::max(height(x->left), height(x->right)) + 1;

    return x;
}

右旋操作与左旋操作类似,具体实现见代码。

2.3 何时旋转

什么时候需要进行旋转操作,当然是失衡的时候。以下列出的是失衡的四种情况,以及为了保持平衡需要做出的旋转决策:

  • LL型,在当前节点的左子节点的左子节点插入新的节点后,当前节点失衡,此时为了保持平衡需要右旋当前节点。
  • RR型,在当前节点的右子节点的右子节点插入新的节点后,当前节点失衡,此时为了保持平衡需要左旋当前节点。
  • LR型,在当前节点的左子节点的右子节点插入新的节点后,当前节点失衡,此时为了保持平衡需要先左旋当前节点的左子节点,再右旋当前节点。
  • RL型,在当前节点的右子节点的左子节点插入新的节点后,当前节点失衡,此时为了保持平衡需要先右旋当前节点的右子节点,再左旋当前节点。

在 LL型 情况中,我们注意到失衡节点的失衡因子为 2,失衡节点的左子节点的失衡因子为 1。类似的,在 RR型 中,失衡节点的失衡因子为 -2,失衡节点的失衡因子为 -1;在 LR型 中,失衡节点的失衡因子为 2,失衡节点的失衡因子为 -1;在 RL型 中,失衡节点的失衡因子为 -2,失衡节点的失衡因子为 1。于是,我们可以根据失衡节点的失衡因子、失衡节点的左子节点的失衡因子、失衡节点的右子节点的失衡因子来判断当前是什么类型,进而做出相应的旋转决策。

平衡二叉树-LL型.drawio
图 2-3 LL型
平衡二叉树-RR型.drawio
图 2-4 RR型
平衡二叉树-LR型.drawio
图 2-5 LR型
平衡二叉树-RL型.drawio
图 2-6 RL型

3 插入节点

插入节点和在二叉查找树中插入节点一样,先进行未命中的查找,将节点插入到合适的位置。然后根据以上四种情况进行旋转以保持平衡二叉树的平衡性。比如在图 2-6 中插入一个新的节点 8,通过二分查找确定节点 8 的位置为节点 6 右子节点。但是插入节点 8 之后,造成根节点 5 的平衡因子失衡了。因为这是 RL型 情况,所以先右旋根节点的右子树,然后再左旋根节点。

需要注意的是,当插入节点导致多个祖先节点失衡,只需要调整距离插入节点最近的失衡节点即可,其他失衡节点会自动平衡。

平衡二叉树-插入-注意事项.drawio (1)
图 3-1 调整最近的失衡节点

实现代码

// 插入节点
Node* insert(Node* node, int key) {
    if (node == nullptr)
        return new Node(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;

    node->height = 1 + std::max(height(node->left), height(node->right));

    int balance = getBalance(node);

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

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

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

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

在上述插入代码中,我们先进行未命中的查找,在递归查找中插入新的节点。接着,更新当前根节点的高度。最后根据当前节点的平衡因子的不同值来确定是哪一个类型,再根据不同的类型进行相应的调整。具体地:

  • 如果当前节点的失衡因子为 2,并且是在当前节点的左子节点的左子节点插入新的节点,则是 LL 型,右旋该节点即可;
  • 如果当前节点的失衡因子为 -2,并且是在当前节点的右子节点的右子节点插入新的节点,则是 RR 型,左旋节点即可;
  • 如果当前节点的失衡因子为 2,并且是在当前节点的左子节点的右子节点插入新的节点,则是 LR 型,先左旋该节点的左子节点,再右旋该节点;
  • 如果当前节点的失衡因子为 -2,并且是在当前节点的右子节点的左子节点插入新的节点,则是 RL 型,先右旋该节点的右子节点,再左旋该节点;

4 删除节点

删除节点操作也需要先进行查找,递归的进行查找。查找到需要删除的节点 root 之后,按照以下情况进行处理:

  • 如果该节点是叶子节点,直接删除该节点;
  • 如果该节点仅有左子节点或者右子节点,则删除对应的子节点;
  • 如果该节点的左右节点都齐全,则用右子节点中最小的节点更新 root,并删除这个右子节点。

接着更新 root 的高度。最后将失衡的节点旋转,保持平衡。依然是根据当前节点与左、右子节点的平衡因子来判断属于哪种旋转情况,然后进行相应的旋转。

实现代码

// 删除节点
Node* deleteNode(Node* 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)) {
            Node* temp = root->left ? root->left : root->right;
            if (temp == nullptr) {
                temp = root;
                root = nullptr;
            } else
                *root = *temp;
            delete temp;
        } else {
            Node* temp = minValueNode(root->right);
            root->key = temp->key;
            root->right = deleteNode(root->right, temp->key);
        }
    }

    if (root == nullptr)
        return root;

    root->height = 1 + std::max(height(root->left), height(root->right));
    int balance = getBalance(root);

    if (balance > 1 && getBalance(root->left) >= 0)
        return rightRotate(root);

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

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

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

5 代码

#include <iostream>
#include <algorithm>

class AVLTree {
private:
    struct Node {
        int key;
        Node* left;
        Node* right;
        int height;
        Node(int k) : key(k), left(nullptr), right(nullptr), height(1) {}
    };

    Node* root;

    int height(Node* n) {
        return n == nullptr ? 0 : n->height;
    }
    

    // 求平衡因子
    int getBalance(Node* n) {
        return n == nullptr ? 0 : height(n->left) - height(n->right);
    }

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

        x->right = y;
        y->left = T2;

        y->height = std::max(height(y->left), height(y->right)) + 1;
        x->height = std::max(height(x->left), height(x->right)) + 1;

        return x;
    }

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

        y->left = x;
        x->right = T2;

        x->height = std::max(height(x->left), height(x->right)) + 1;
        y->height = std::max(height(y->left), height(y->right)) + 1;

        return y;
    }

    // 插入节点
    Node* insert(Node* node, int key) {
        if (node == nullptr)
            return new Node(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;

        node->height = 1 + std::max(height(node->left), height(node->right));

        int balance = getBalance(node);

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

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

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

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

    // 获得最小节点值
    Node* minValueNode(Node* node) {
        Node* current = node;
        while (current->left != nullptr)
            current = current->left;
        return current;
    }

    // 删除节点
    Node* deleteNode(Node* 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)) {
                Node* temp = root->left ? root->left : root->right;
                if (temp == nullptr) {
                    temp = root;
                    root = nullptr;
                } else
                    *root = *temp;
                delete temp;
            } else {
                Node* temp = minValueNode(root->right);
                root->key = temp->key;
                root->right = deleteNode(root->right, temp->key);
            }
        }

        if (root == nullptr)
            return root;

        root->height = 1 + std::max(height(root->left), height(root->right));
        int balance = getBalance(root);

        if (balance > 1 && getBalance(root->left) >= 0)
            return rightRotate(root);

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

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

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

    // 查找节点
    Node* search(Node* root, int key) {
        if (root == nullptr || root->key == key)
            return root;

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

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

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

public:
    AVLTree() : root(nullptr) {}

    // 插入节点的对外接口
    void insert(int key) {
        root = insert(root, key);
    }

    // 删除节点的对外接口
    void deleteNode(int key) {
        root = deleteNode(root, key);
    }

    // 搜索节点的对外接口
    bool search(int key) {
        return search(root, key) != nullptr;
    }

    // 中序遍历的对外接口
    void inOrder() {
        inOrder(root);
        std::cout << std::endl;
    }
};

int main() {
    AVLTree tree;

    tree.insert(10);
    tree.insert(20);
    tree.insert(30);
    tree.insert(40);
    tree.insert(50);
    tree.insert(25);

    std::cout << "中序遍历后的AVL树: ";
    tree.inOrder();

    tree.deleteNode(10);

    std::cout << "删除节点10后的AVL树: ";
    tree.inOrder();

    int key = 25;
    if (tree.search(key))
        std::cout << "找到节点: " << key << std::endl;
    else
        std::cout << "未找到节点: " << key << std::endl;

    return 0;
}

参考资料

【视频】平衡二叉树(AVL树)


写在最后

如果您发现文章有任何错误或者对文章有任何疑问,欢迎私信博主或者在评论区指出 💬💬💬。

如果大家觉得有些地方需要补充,欢迎评论区交流。

最后,感谢您的阅读,如果有所收获的话可以给我点一个 👍 哦。

  • 29
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
平衡二叉树是一种特殊的二叉搜索树,它具有以下特点: 1. 左子树和右子树的高度差不超过 1; 2. 左子树和右子树都是平衡二叉树。 常见的平衡二叉树有 AVL 树、红黑树等。 平衡二叉树基本算法验证包括以下部分: 1. 插入节点:在插入节点时,需要保证插入后树仍然是平衡的。具体操作是:首先按照二叉搜索树的插入方法将节点插入到树中,然后从插入节点开始向上逐层检查,如果检查到某个节点不平衡,就进行旋转操作,使得该节点重新平衡。旋转操作包括左旋右旋、左右旋和右左旋四种,具体实现可参考 AVL 树、红黑树等平衡二叉树旋转操作; 2. 删除节点:在删除节点时,同样需要保证删除后树仍然是平衡的。具体操作是:先按照二叉搜索树的删除方法将节点删除,然后从删除节点的父节点开始向上逐层检查,如果检查到某个节点不平衡,就进行旋转操作,使得该节点重新平衡; 3. 查找节点:在平衡二叉树中查找节点的方法与二叉搜索树相同,具体操作是从根节点开始,比较待查找节点与当前节点的大小关系,然后根据比较结果向左子树或右子树遍历,直到找到目标节点或遍历到叶子节点为止。 以上就是平衡二叉树基本算法验证的内容。需要注意的是,不同的平衡二叉树可能存在一些细微的差异,具体实现时需要结合具体的平衡二叉树来进行调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wang_nn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值