大话数据结构学习笔记 - 查找之平衡二叉树(AVL)及其C实现

19 篇文章 0 订阅
16 篇文章 1 订阅

大话数据结构学习笔记 - 查找之平衡二叉树(AVL)及其C实现

平衡二叉树(AVL树)

平衡二叉树(Self-Balancing Binary Search TreeHeight-Balanced Binary Search Tree)是一种二叉排序树, 其中每一个节点的左子树和右子树的高度差至多等于1. 平衡二叉树是高度平衡的二叉排序树,高度平衡即要么是空树,要么其左子树和右子树都是平衡二叉树,且左子树和右子树的高度差不超过1

  • 平衡因子BF(Balance Factor): 二叉树结点的左子树深度减去右子树深度的值
  • 最小不平衡树: 距离插入节点最近的,且平衡因子的绝对值大于1的结点为根的子树

二叉树结构

typedef struct AVLTNode
{
    ElementType data;
    int height;
    struct AVLTNode *lchild, *rchild;
}AVLTNode, *AVLTree;

旋转

节点的插入和删除都会影响到平衡二叉树的结构,进而影响到某些结点的平衡因子, 若平衡因子的取值不是{-1, 0, 1}的话,表明该AVL树失去平衡,则此时需要通过旋转使AVL树重新平衡。 失衡的情况分为以下几种

Search_AVLTree_Rotate_abstract_1

上面四种都是失去平衡的AVL树,依次记为LL, LR, RL, RR。对于每种失衡情况,还有其他的形式

Search_AVLTree_Rotate_abstract_2
总得来说,失去平衡的AVL树一定符合上述四种类型

  • LLLeftLeft, 也称为左左。 在某结点的左子树的左子树上插入或在某结点的右子树上删除一个节点后,导致该结点的平衡因子变为2, 则该结点成为最小不平衡树的根节点,该最小不平衡树失去平衡

    比如上图LL情况,在根节点8的左子树的左子树上插入节点1或节点3,都会导致以8为根节点的树的平衡因子为2, 故8为根节点的树为最小不平衡树,则此时需要对该最小不平衡树的根节点进行LL单旋

  • RRRightRight, 也称为右右。 在某结点的右子树的右子树上插入或在某结点的左子树上删除一个节点后,导致该结点的平衡因子变为-2, 则该结点成为最小不平衡树的根节点,该最小不平衡树失去平衡

    比如上图RR情况,在根节点8的右子树的右子树上插入节点13或节点15,都会导致以8为根节点的树的平衡因子为-2, 故8为根节点的树为最小不平衡树,则此时需要对该最小不平衡树的根节点进行RR单旋

  • LRLeftRight, 也称为左右。 在某结点的左子树的右子树上插入或在某结点的右子树上删除一个节点后,导致该结点的平衡因子变为2, 则该结点成为最小不平衡树的根节点,该最小不平衡树失去平衡

    比如上图LR情况,在根节点8的左子树的右子树上插入节点5或节点7,都会导致以8为根节点的树的平衡因子为2, 故8为根节点的树为最小不平衡树,则此时需要对该最小不平衡树的根节点的左子树进行LL单旋, 然后再对根节点进行RR单旋

  • LRRightLeft, 也称为右左。 在某结点的右子树的左子树上插入或在某结点的左子树上删除一个节点后,导致该结点的平衡因子变为-2, 则该结点成为最小不平衡树的根节点,该最小不平衡树失去平衡

    比如上图RL情况,在根节点8的右子树的左子树上插入节点9或节点11,都会导致以8为根节点的树的平衡因子为-2, 故8为根节点的树为最小不平衡树,则此时需要对该最小不平衡树的根节点的右子树进行RR单旋, 然后再对根节点进行LL单旋

LL 单旋

LL失衡情况,可通过一次旋转让最小不平衡树恢复平衡,如下图,旋转之前 k2的平衡因子为2, 即k2为最小不平衡树的根节点, 然后将最小不平衡树围绕其根节点k2旋转,旋转为右图所示。

注意:虽然是顺时针向右旋转,但不可记混,此为LL单旋

Search_AVLTree_Rotate_LL_Rotate

LL单旋代码
/*
 * LL 单旋
 * 返回值: 旋转后的节点
 */
AVLTNode* LL_Rotate(AVLTNode *p)
{
    AVLTNode *q = p->lchild;
    p->lchild = q->rchild;
    q->rchild = p;

    p->height = MAX(GetHeight(p->lchild), GetHeight(p->rchild)) + 1;
    q->height = MAX(GetHeight(q->lchild), GetHeight(q->rchild)) + 1;

    return q;
}
RR单旋

RR失衡情况,可通过一次旋转让最小不平衡树恢复平衡,如下图,旋转之前 k1的平衡因子为-2, 即k1为最小不平衡树的根节点, 然后将最小不平衡树围绕其根节点k1旋转,旋转为右图所示。

Search_AVLTree_Rotate_RR_Rotate

注意:虽然是逆时针向左旋转,但不可记混,此为RR单旋

RR单旋代码
/*
 * RR 单旋
 * 返回值: 旋转后的节点
 */
AVLTNode* RR_Rotate(AVLTNode *p)
{
    AVLTNode *q = p->rchild;
    p->rchild = q->lchild;
    q->lchild = p;

    p->height = MAX(GetHeight(p->lchild), GetHeight(p->rchild)) + 1;
    q->height = MAX(GetHeight(q->lchild), GetHeight(q->rchild)) + 1;

    return q;
}
LR双旋

LR失衡情况, 需通过两次旋转才能让最小不平衡树恢复平衡。如下图所示,第一次旋转是围绕最小不平衡树根节点k3的左子树根节点k1进行的RR单旋, 第二次是围绕最小不平衡树根节点k3进行的LL单旋

Search_AVLTree_Rotate_LR_Rotate

LR双旋代码
/*
 * LR双旋: 先左后右双向旋转
 * 返回值: 旋转后的节点
 */
AVLTNode* LR_Rotate(AVLTNode *p)
{
    p->lchild = RR_Rotate(p->lchild);
    return LL_Rotate(p);
}
RL双旋

RL失衡情况, 需通过两次旋转才能让最小不平衡树恢复平衡。如下图所示,第一次旋转是围绕最小不平衡树根节点k1的右子树根节点k3进行的LL单旋, 第二次是围绕最小不平衡树根节点k1进行的RR单旋

Search_AVLTree_Rotate_RL_Rotate

RL双旋代码
/*
 * RL双旋: 先右后左双向旋转
 * 返回值: 旋转后的结点
 */
AVLTNode* RL_Rotate(AVLTNode *p)
{
    p->rchild = LL_Rotate(p->rchild);
    return RR_Rotate(p);
}

插入

插入某结点到AVL

/*
 * 插入节点到 AVL 树中, 并返回根节点
 */
AVLTree Insert(AVLTree T, ElementType data)
{
    if(T == NULL)  /* 若树为空或找到插入值的位置 */
    {
        T = CreateNode(data);
        if(!T)
            return NULL;
    }
    else if(data < T->data)  /* 将节点插入到左子树 */
    {
        T->lchild = Insert(T->lchild, data);  /* 递归寻找插入节点位置 */
        if (GetHeight(T->lchild) - GetHeight(T->rchild) == 2)  /* 判断插入后左子树与右子树的平衡因子是否为 2 */
        {
            if (data < T->lchild->data)  /* 表示插入到第一个失衡根节点的左子树的左子树上 */
                T = LL_Rotate(T);
            else                         /* 表示插入到第一个失衡根节点的左子树的右子树上 */
                T = LR_Rotate(T);
        }
    }
    else if(data > T->data)  /* 将节点插入到右子树 */
    {
        T->rchild = Insert(T->rchild, data);  /* 递归寻找插入节点位置 */
        if (GetHeight(T->rchild) - GetHeight(T->lchild) == 2)  /* 判断插入后左子树与右子树的平衡因子是否为 -2 */
        {
            if (data > T->rchild->data)  /* 表示插入到第一个失衡根节点的右子树的右子树上 */
                T = RR_Rotate(T);
            else                         /* 表示插入到第一个失衡根节点的右子树的左子树上 */
                T = RL_Rotate(T);
        }
    }
    else
        printf("Fail to insert the same node!");

    T->height = MAX(GetHeight(T->lchild), GetHeight(T->rchild)) + 1;
    return T;
}

搜索

/*
 * 查找某个值, 返回该结点
 */
AVLTNode* Search(AVLTree T, ElementType data)
{
    if(!T || data == T->data)
        return T;
    else if(data < T->data)
        return Search(T->lchild, data);
    else
        return Search(T->rchild, data);
}

删除

删除AVL树中某结点

/*
 * 删除某个值
 */
AVLTree Delete(AVLTree T, ElementType data)
{
    /* 判断 T 是否为空 */
    if(T == NULL)
        return NULL;
    else if(data < T->data)  // 若删除的值小于当前结点所指向的值
    {
        T->lchild = Delete(T->lchild, data);  // 在左子树中递归查找要删除的值所在的结点
        if(GetHeight(T->rchild) - GetHeight(T->lchild) == 2)  // 删除节点后, 判断结点是否失衡, 即平衡因子是否为 -2
        {
            AVLTNode *rchild = T->rchild;
            /* 比较以当前结点右子树为根节点的子树的高度, 判断应该采用哪种旋转类型 */
            if(GetHeight(rchild->rchild) > GetHeight(rchild->lchild))
                T = RR_Rotate(T);
            else
                T = RL_Rotate(T);
        }
    }
    else if(data > T->data)  // 若删除的值小于当前结点所指向的值
    {
        T->rchild = Delete(T->rchild, data);  // 在右子树中递归查找要删除的值所在的结点
        if(GetHeight(T->lchild) - GetHeight(T->rchild) == 2)  // 删除节点后, 判断结点是否失衡, 即平衡因子是否为 2
        {
            AVLTNode *lchild = T->lchild;
            /* 比较以当前结点左子树为根节点的子树的高度, 判断应该采用哪种旋转类型 */
            if(GetHeight(lchild->lchild) > GetHeight(lchild->rchild))
                T = LL_Rotate(T);
            else
                T = LR_Rotate(T);
        }
    }
    else // 找到删除节点
    {
        if(T->lchild && T->rchild)  /* 左右子树均不空 */
        {
            // 若左子树高度高于右子树, 则选取待删除节点的前驱节点取代待删除节点
            if(GetHeight(T->lchild) > GetHeight(T->rchild))
            {
                AVLTNode *tmp = GetMax(T->lchild);
                T->data = tmp->data;
                T->lchild = Delete(T->lchild, tmp->data);
            }
            else  // 否则选取待删除节点的后继节点取代待删除节点
            {
                AVLTNode *tmp = GetMin(T->rchild);
                T->data = tmp->data;
                T->rchild = Delete(T->rchild, tmp->data);
            }
        }
        else  // 若待删除节点只有左子树或右子树, 或为叶子节点
        {
            AVLTNode *tmp = T;
            T = T->lchild != NULL ? T->lchild : T->rchild;
            free(tmp);
        }
    }
    return T;
}

测试

int arr[]= {3,2,1,4,5,6,7,16,15,14,13,12,11,10,8,9}, ilen = LENGTH(arr);
AVLTree tree = NULL;
for(int i = 0; i < ilen; i++)
    tree = Insert(tree, arr[i]);
PreOrder(tree);
InOrder(tree);
输出
== 前序遍历: 7 4 2 1 3 6 5 13 10 9 8 11 15 14 16 
== 中序遍历: 1 2 3 4 5 6 7 8 9 10 11 13 14 15 16 

Search_AVLTree_test

关于上述操作用到的其他函数,都在源代码中实现,故可通过查阅源代码了解

时间复杂度

  • 插入、删除和查找的时间复杂度都为 O(logn) O ( l o g n )

源代码

AVLTree

结语

平衡二叉树作为查找算法还是很重要滴,并且有了AVL树的知识后,对于后面的B-Tree以及B+Tree和红黑树都有很大的帮助。一定要仔细理解,并且在学习每个操作的过程中,动手模拟操作过程,有助于理解以及加深印象。加油, Fighting

参考

AVL树(一)之 C语言详解

大话数据结构

平衡二叉树是一种特殊的二叉树,它的左右子树的高度差不超过1。AVL树是一种自平衡的二叉搜索树,它的高度始终保持在O(log n)。 下面是C语言实现平衡二叉树AVL树)的代码: ``` #include <stdio.h> #include <stdlib.h> /* 定义平衡二叉树节点结构体 */ struct AVLNode { int data; // 存储的数据 int height; // 节点高度 struct AVLNode *leftChild; // 左子树 struct AVLNode *rightChild; // 右子树 }; /* 获取节点高度 */ int getHeight(struct AVLNode *node) { if (node == NULL) { return -1; } else { return node->height; } } /* 获取节点平衡因子 */ int getBalanceFactor(struct AVLNode *node) { if (node == NULL) { return 0; } else { return getHeight(node->leftChild) - getHeight(node->rightChild); } } /* 更新节点高度 */ void updateHeight(struct AVLNode *node) { node->height = 1 + (getHeight(node->leftChild) > getHeight(node->rightChild) ? getHeight(node->leftChild) : getHeight(node->rightChild)); } /* 右旋操作 */ struct AVLNode *rotateRight(struct AVLNode *node) { struct AVLNode *newRoot = node->leftChild; node->leftChild = newRoot->rightChild; newRoot->rightChild = node; updateHeight(node); updateHeight(newRoot); return newRoot; } /* 左旋操作 */ struct AVLNode *rotateLeft(struct AVLNode *node) { struct AVLNode *newRoot = node->rightChild; node->rightChild = newRoot->leftChild; newRoot->leftChild = node; updateHeight(node); updateHeight(newRoot); return newRoot; } /* 插入操作 */ struct AVLNode *insert(struct AVLNode *root, int data) { if (root == NULL) { root = (struct AVLNode *) malloc(sizeof(struct AVLNode)); root->data = data; root->height = 0; root->leftChild = NULL; root->rightChild = NULL; } else if (data < root->data) { root->leftChild = insert(root->leftChild, data); if (getHeight(root->leftChild) - getHeight(root->rightChild) == 2) { if (data < root->leftChild->data) { root = rotateRight(root); } else { root->leftChild = rotateLeft(root->leftChild); root = rotateRight(root); } } } else if (data > root->data) { root->rightChild = insert(root->rightChild, data); if (getHeight(root->rightChild) - getHeight(root->leftChild) == 2) { if (data > root->rightChild->data) { root = rotateLeft(root); } else { root->rightChild = rotateRight(root->rightChild); root = rotateLeft(root); } } } updateHeight(root); return root; } /* 中序遍历 */ void inOrderTraversal(struct AVLNode *root) { if (root != NULL) { inOrderTraversal(root->leftChild); printf("%d ", root->data); inOrderTraversal(root->rightChild); } } int main() { struct AVLNode *root = NULL; int data[] = {5, 2, 8, 1, 3, 6, 9}; int len = sizeof(data) / sizeof(data[0]); int i; for (i = 0; i < len; i++) { root = insert(root, data[i]); } inOrderTraversal(root); return 0; } ``` 以上代码实现平衡二叉树的插入和中序遍历操作。在插入操作中,根据插入节点的值和当前节点的值的大小关系,不断递归向左或向右子树进行插入操作,并在递归返回时更新节点高度和进行平衡操作。在平衡操作中,根据节点的平衡因子进行旋转操作,使树重新平衡。在中序遍历操作中,按照左子树、根节点、右子树的顺序遍历树中的节点,输出节点的值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值