<二叉树>平衡二叉树

平衡二叉树是一颗合理的二叉排序树(BST):
    二叉排序树失衡时,会退化成链表。导致查找的复杂度变为O(n)。
    为了解决该问题,将二分法 + 二叉排序树 = 平衡二叉树

平衡二叉树定义(AVL):它或者是一颗空树,或者具有以下性质的二叉排序树:
    它的左子树和右子树的深度之差(平衡因子)的绝对值不超过1,
    且它的左子树和右子树都是一颗平衡二叉树。
一棵AVL树有如下必要条件:
    条件一:它必须是二叉查找树。
    条件二:每个节点的左子树和右子树的高度差至多为1。
    (平衡:节点均匀分布在左右两侧,减少了数的深度也就减少了查找的次数)
保持平衡
    AVL树的查找、插入、删除操作在平均和最坏的情况下都是O(logn),这得益于它时刻维护着二叉树的平衡。
    不平衡的二叉查找树在查找时的效率是很低的,因此,AVL如何维护二叉树的平衡是我们的学习重点。
    AVL树的平衡调整
如何构建一颗平衡二叉树
    1.本质上与构建二叉排序树一致
    2.在该过程中,如果发现树不符合特性,需要进行调整,调整需要用到树的高度
        所以我们节点的结构体当中需要加一个字段来标识当前树的高度
    3.调整方法:
        LL、RR、LR、RL
        1.找到失衡树的根节点root
        2.找到导致失衡的节点node,
        3.判断node在root的哪一侧,判断node在root孩子的哪一侧
        RR:
            1                     2
             \                   / \
              2                 1   3
             / \                 \
           4     3                4
            root=1、node=3、child=2
            取中间的节点child,使它的父亲成为它的左孩子,如果它有左孩子的话,
            那么这个左孩子连接到父亲的右孩子上
            root->rchild = child->lchild;
            child->lchild = root;

        LL:
            5                    4
           /                    / \
          4                    3   5
         / \                      /
        3   6                    6
            root=5、node=3、child=4
            取中间的节点,使它的父亲成为它的右孩子,如果它有右孩子的话,
            那么这个右孩子连接到父亲的左孩子上
            root->lchild = node->rchild;
            node->rchild = root;

        eg(大小树失衡):
            4              4
           / \            / \ 
          3   5          2   5
         /              / \
        2              1   3
       /
      1

        LR:
            8              8
           / \            / \
          7   9          6   9
         /              / \
        5              5   7
         \
          6
        取最最后一个节点作为父节点,将它的父亲作为自己的左孩子,将它父亲的父亲作为自己的右孩子
        如果它有左孩子或者右孩子的话,它原先的左孩子,连接到父亲的右孩子
        RL:
            1              6
             \            / \
              8          1   8
             /
            6
#include <stdio.h>
#include <stdlib.h>

typedef struct TreeNode 
{
    // 数据域
    int data;
    // 节点高度
    int height;
    // 左子树指针地址
    struct TreeNode* lchild;
    // 右子树指针地址
    struct TreeNode* rchild;
}TreeNode;

int getHeight(TreeNode* node) 
{
    return node ? node->height : 0;
}

int max(int a, int b)
{
    return a > b ? a : b;
}

// 传入中间节点的父节点node
void rrRotation(TreeNode* node, TreeNode** root) 
{
    // 中间节点temp
    TreeNode* temp = node -> rchild;
    // 如果它有左孩子的话,那么这个左孩子连接到父亲的右孩子上
    node->rchild = temp -> lchild;
    // 取中间的节点child,使它的父亲成为它的左孩子
    temp->lchild = node;
    // 把中间节点变为根节点,根节点指针指向新的根节点
    *root = temp;

    node->height = max(getHeight(node -> lchild), getHeight(node -> rchild)) + 1;
    temp->height = max(getHeight(temp -> lchild), getHeight(temp -> rchild)) + 1;

}

void llRotation(TreeNode* node, TreeNode** root) 
{
    TreeNode* temp = node -> lchild;
    node -> lchild = temp -> rchild; 
    temp -> rchild = node;
    *root = temp;
    node -> height = max(getHeight(node -> lchild), getHeight(node -> rchild)) + 1;
    temp -> height = max(getHeight(temp -> lchild), getHeight(temp -> rchild)) + 1;
}

// 传入结构体指针的地址
void avlInsert(TreeNode** T, int data)
{
    // 第一次根节点未初始化时进来,创建左右子树时也会进来
    if (*T == NULL)
    {
        // 给结构体指针指向的变量开辟空间
        *T = (TreeNode*)malloc(sizeof(TreeNode));
        (*T) -> data = data;
        (*T) -> height = 0;
        (*T) -> lchild = NULL;
        (*T) -> rchild = NULL;
    }
    else if(data < (*T)->data)// 当前插入的节点在当前节点的左侧
    {
        // 传入当期节点左子树指针的地址
        avlInsert(&(*T)->lchild, data);
        // 拿到当前节点左右子树的高度
        int lHeight = getHeight((*T) -> lchild);
        int rHeight = getHeight((*T) -> rchild);
        // 判断高度差
        if (lHeight - rHeight == 2)
        {
            // 判断node在root孩子的哪一侧
            if (data < (*T)->lchild->data)
            {
                // LL 调整
                llRotation(*T, T);
            }
            else 
            {
                // LR 调整
                rrRotation((*T) -> lchild, &(*T) -> lchild);
                llRotation(*T, T);
            }
        }
    }
    else if(data > (*T)->data)// 当前插入的节点在当前节点的右侧
    {
        // 传入当期节点右子树指针的地址
        avlInsert(&(*T) -> rchild, data);
        // 拿到当前节点左右子树的高度
        int lHeight = getHeight((*T) -> lchild);
        int rHeight = getHeight((*T) -> rchild);
        // 判断高度差
        if (rHeight - lHeight == 2) 
        {
            // 判断node在root孩子的哪一侧
            if (data > (*T)->rchild->data)
            {
                // RR 调整
                rrRotation(*T, T);
            }
            else
            {
                // RL 调整
                llRotation((*T) -> rchild, &(*T) -> rchild);
                rrRotation(*T, T);
            }
        }
    }

    // 左右子树最大的高度+1
    (*T)->height = max(getHeight((*T) -> lchild), getHeight((*T) -> rchild)) + 1;

}

void preOrder(TreeNode* T)
{
    if(T)
    {
        // 先访问根节点,再访问左子树,最后访问右子树 
        printf("%d ", T->data);
        preOrder(T->lchild);
        preOrder(T->rchild);
    }
}

int main() 
{
    // 结构体指针
    TreeNode* T = NULL;
    int nums[5] = {1,8,6,7,10};
    for (int i = 0; i < 5; i++)
    {
        // 传入结构体指针的地址
        avlInsert(&T, nums[i]);
    }

    preOrder(T);
    printf("\n");
}

/*
BST:
    1
      \
       8
      /
     6
      \
       7
        \
        10

AVL:
    6
   / \ 
  1   8
     / \
    7  10

preorder:6 1 8 7 10
*/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值