【从零开始数据结构】平衡二叉树

81 阅读5分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

平衡二叉树

平衡二叉树首先具备了二叉搜索树的特性,因为二叉搜索树并未对树的高做限制,只要求了左小右大,这就可能在极端情况下,出现左斜树或右斜树,这是查找就从O(logn)的期望退化成了O(n),即使不那么极端,树的高度也是没办法控制在一个稳定值上,这就使得当数据量庞大时,查找耗时过多。

这就产生了如何对一棵树进行优化,使其高度稳定,首先需要对树的高度进行获取和记录,通过左旋和右旋使二叉搜索树达到平衡,也就是使各节点左子树和右子树的高度差不超过1,这样的查找时间复杂度就能稳定在O(logn)级别;

1. 定义

“平衡因子(Balanced Factor,简称BF)”BF(T) =Hl - Hr,其中Hl 和Hr分别为T的左、右子树的高度。

平衡二叉树(AVL树):空树或者任一结点左、右子树高度差的绝对值不超过1的搜索二叉树,即|BF(T)| <= 1

AVL树的最大高度能达到logn吗?

给定结点数为 n 的AVL树的最大高度为O(logn)!

2. 平衡调整

  • 右单旋:“麻烦结点”不平衡的“发现者” 右子树的右边,因而叫 RR插入 ,需要 RR旋转

    如下图,在A的右孩子B的右子树BR(不一定为空)中插入结点(图中阴影部分所示)而导致不平衡( h 表示子树的深度)。

    这种情况调整如下:

    • 将A的右孩子B提升为新的根结点;
    • 将原来的根结点A降为B的左孩子
    • 各子树按大小关系连接(AL和BR不变,BL调整为A的右子树)。

    在这里插入图片描述

  • 左单旋:“麻烦结点”* 在 不平衡的“发现者” 左子树的左边,因而叫 LL插入 ,需要 LL旋转

    在A的左孩子B的左子树BL(不一定为空)中插入结点(图中阴影部分所示)而导致不平衡( h 表示子树的深度)。

    这种情况调整如下:

    • 将A的左孩子B提升为新的根结点;
    • 将原来的根结点A降为B的右孩子;
    • 各子树按大小关系连接(BL和AR不变,BR调整为A的左子树)。

    在这里插入图片描述

  • 左右双旋:“麻烦结点”不平衡的“发现者” 左子树的右边,因而叫 LR插入 ,需要 LR旋转

    在A的左孩子B的右子树(根结点为C,不一定为空)中插入结点(图中两个阴影部分之一)而导致不平衡( h 表示子树的深度)。

    这种情况调整如下:

    • 将B的右孩子C提升为新的根结点;
    • 将原来的根结点A降为C的右孩子;
    • 各子树按大小关系连接(BL和AR不变,CL和CR分别调整为B的右子树和A的左子树)。

    在这里插入图片描述

  • 右左双旋:“麻烦结点”不平衡的“发现者” 右子树的左边,因而叫 RL插入 ,需要 RL旋转

    在A的右孩子B的左子树(根结点为C,不一定为空)中插入结点(图中两个阴影部分之一)而导致不平衡( h 表示子树的深度)。

    这种情况调整如下:

    • 将B的左孩子C提升为新的根结点;
    • 将原来的根结点A降为C的左孩子;
    • 各子树按大小关系连接(AL和BR不变,CL和CR分别调整为A的右子树和B的左子树)。

    在这里插入图片描述

3. 代码实现

// 对着图去旋转会很清晰
#include <iostream>

using namespace std;

typedef int ElemType;
    
struct AVLNode {
    ElemType data;
    int height;	// 高度,通过高度的比较可以得到是否平衡
    AVLNode* left;
    AVLNode* right;

    AVLNode(ElemType data = 0) :
        data(data), height(1), left(nullptr), right(nullptr) { }
};

// 获取树的最大高度
int getHeightAVL(AVLNode* T)
{
    if (!T) {
        return 0;
    }
    return 1+ max(getHeightAVL(T->left), getHeightAVL(T->right));
}

// LL旋转
// 返回新父结点,T为发现失衡的最低的结点
AVLNode* LL_rotate(AVLNode* T)	
{
    AVLNode* newT = T->left;
    T->left = newT->right;
    newT->right = T;

    // 高度必须重新获取
    T->height = 1 + max(getHeightAVL(T->left), getHeightAVL(T->right));
    newT->height = 1 + max(getHeightAVL(newT->left), getHeightAVL(newT->right));
    return newT;
}

// RR旋转
AVLNode* RR_rotate(AVLNode* T)
{
    AVLNode* newT = T->right;
    T->right = newT->left;
    newT->left = T;

    T->height = 1 + max(getHeightAVL(T->left), getHeightAVL(T->right));
    newT->height = 1 + max(getHeightAVL(newT->left), getHeightAVL(newT->right));
    return newT;
}

// LR旋转
// 先RR旋转,然后LL旋转
AVLNode* LR_rotate(AVLNode* T)
{
    AVLNode* new1T = T->left;
    T->left = RR_rotate(new1T);
    return LL_rotate(T);
}

// RL旋转
// 先LL旋转,然后RR旋转
AVLNode* RL_rotate(AVLNode* T)
{
    AVLNode* new1T = T->right;
    T->right = LL_rotate(new1T);
    return RR_rotate(T);
}

// 判断是否平衡
int getBalanceAVL(AVLNode* T)
{
    if (!T) {
        return 0;
    }
    return getHeightAVL(T->left) - getHeightAVL(T->right);
}


// 插入结点
AVLNode* insertAVL(AVLNode* node, ElemType key)
{
    // 先向AVL树中插入key
    if (!node) {
        node = new AVLNode(key);
        return node;
    }

    if (key < node->data) {
        node->left = insertAVL(node->left, key);
    }
    else if (key > node->data) {
        node->right = insertAVL(node->right, key);
    }
    else {
        return node;
    }
    // 重新调整结点的高度
    node->height = 1 + max(getHeightAVL(node->left), getHeightAVL(node->right));

    // 插入之后可能发生不平衡的状况,开始调整AVL树
    int balance = getBalanceAVL(node);

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

    if (balance > 1 && key > node->left->data)  // LR型
        return LR_rotate(node);

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

    if (balance < -1 && key < node->right->data)    // RL型
        return RL_rotate(node);
    
    return node;
}


// 删除操作
// 找到最小的结点
AVLNode* minDataNode(AVLNode* node)
{
    AVLNode* cur = node;
    while (cur->left) {
        cur = cur->left;
    }
    return cur;
}

AVLNode* deleteNode(AVLNode* node, ElemType key)
{
    if (!node) {   // 空树
        return node;
    }

    if (key < node->data) {
        node->left = deleteNode(node->left, key);
    }
    else if (key > node->data) {
        node->right = deleteNode(node->right, key);
    }
    // 这里比较关键,比较绕需要梳理画图
    else {  // 现在已经找到要删除的结点了,去删除它
        // 如果左子树为空或右子树为空
        if ((!node->left) || (!node->right)) {  
            // temp指向存在的子树
            AVLNode* temp = node->left ? node->left : node->right;

            if (!temp) {    // 左右子树都空
                temp = node;
                node = nullptr;
            }
            else {  
                // 这里并不是删除结点
                // 而是将它存在的子树的结点值赋给我们要“删除”的结点
                // 然后删除子树结点
                *node = *temp;
            }
            delete temp;
        }
        // 如果左右子树都存在
        else {
            AVLNode* temp = minDataNode(node->right);
            node->data = temp->data;
            node->right = deleteNode(node->right, temp->data);
        }
    }

    if (!node) {
        return node;
    }

    // 重新调整结点的高度
    node->height = 1 + max(getHeightAVL(node->left), getHeightAVL(node->right));

    // 插入之后可能发生不平衡的状况,开始调整AVL树
    int balance = getBalanceAVL(node);

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

    if (balance > 1 && key > node->left->data)  // LR型
        return LR_rotate(node);

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

    if (balance < -1 && key < node->right->data)    // RL型
        return RL_rotate(node);

    return node;
}

参考:

  1. 月雲之霄 blog.csdn.net/isunbin/art…

  2. salt.Zhang blog.csdn.net/qq_34930032…

  3. 五分钟学算法 mp.weixin.qq.com/s/AweeCscNo…