平衡二叉树(AVL树)

来源:互联网 发布:小白自学编程 编辑:程序博客网 时间:2024/06/06 04:53

前言

之前看Nginx的红黑树结构,发现对平衡二叉树的相关内容基本都忘光了。当然,以前也就摸过皮毛,所以再学习学习。

AVL树

平衡二叉树,一种高度平衡的二叉排序树,它要么是一颗空树,要么每一节点的左子树左子树与右子树高度差绝对值至多为1。
因此,判断一刻平衡二叉树,两点:
  1. 首先是二叉排序树;
  2. 每一节点的左右子树深度只差的绝对值不超过1,这个差值我们称为平衡因子BF(Balance Factor)
距离插入节点最近的,且平衡因子绝对值大于1的节点为根的子树,称为最小不平衡子树

用书中图表示下:
新插入节点为37,则以58为根节点的子树即为最小不平衡子树。


AVL树实现原理

基本思想:在构建二叉排序树的过程中,每当插入一个节点时,先检查是否因插入而破坏了树的平衡性,若是,则找出最小不平衡子树。然后,在保持二叉排序树特性的前提下,调整最小不平衡子树中各节点之间的连接关系,进行对应旋转,使之成为新的平衡子树。

调整不平衡子树的对应情况如下:(左上角为临时的BF值)
  • a) BF值均为正,将树右转
             
  • b) BF值均为负,将树左转
             
  • c) BF值有正有负即不统一,则先旋转为统一,再重复a) 或 b),即双旋转
             

AVL树实现算法

先定义下AVL树节点结构以及预定宏:
#define    status  int#define    TRUE    1   #define    FALSE   0   #define    LH      +1   /* 左高 */#define    EH      0    /* 等高 */#define    RH      -1   /* 右高 */typedef struct avl_node_s  avl_node_t;struct avl_node_s{    int data;    int bf;    avl_node_t *lchild;    avl_node_t *rchild;};
向右旋转方法:
/* 参考向右旋转图 */void avl_right_rotate(avl_node_t **P){    avl_node_t *L = NULL;        /* L为向右旋转后的根节点 */    L = (*P)->lchild;    (*P)->lchild = L->rchild;    L->rchild = (*P);    *P = L;}
此源码即为右旋操作,参考上图a),意为传入一个二叉排序树p,将其左孩子节点定义为L,将L的右子树变为P的左子树,再将P改成L的右子树,最后L替换P作为根节点。

同理,向左旋转方法:
void avl_left_rotate(avl_node_t **P){    avl_node_t *R = NULL;        R = (*P)->rchild;    (*P)->rchild = R->lchild;    R->lchild = (*P);    *P = R;}
下面是左旋转保持平衡的代码:
/* 算法结束时,指针P指向新的根节点 */void avl_left_balance(avl_node_t **P){    avl_node_t *L = NULL;    avl_node_t *Lr = NULL;    L = (*P)->lchild;    switch (L->bf)    {        case LH:                   // 这里相当于上面的右转方法            (*P)->bf = L->bf = EH;            avl_right_rotate(P);            break;        case RH:                   // 这里相当于BF不统一,原理同上面的双旋转            Lr = L->rchild;            switch (Lr->bf)        // 这里用于修改传入的根节点P及其左孩子的平衡因子,不是很好理解,具体下面用图解释~            {                case LH:           // 下图(a)                    (*P)->bf = RH;                    L->bf = EH;                    break;                case EH:           // 下图(b)                    (*P)->bf = L->bf = EH;                    break;                case RH:           // 下图(c)                    (*P)->bf = EH;                    L->bf = LH;                    break;            }            Lr->bf = EH;            avl_left_rotate(&(*P)->lchild);            avl_right_rotate(P);    }   }
这段代码最不好理解的就是双旋转时,对于根节点及其左孩子最后的BF值修改。这里头必须自己先理清楚旋转之后的树结构,然后才能定下BF值。另外这有一个很重要的前提,就是必须保证根节点P是最小不平衡子树的根。好吧,我是花了很久时间去理解这个了。如下面些图:
  • 情形(a) 
             
  • 情形(b)
             
  • 情形(c)
             
同理,右旋转保持平衡代码:
void avl_right_balance(avl_node_t **P){    avl_node_t *R = NULL;    avl_node_t *Rl = NULL;        R = (*P)->rchild;    switch (R->bf)    {        case RH:            (*P)->bf = R->bf = EH;            avl_left_rotate(P);            break;        case LH:            Rl = R->lchild;            switch (Rl->bf)            {                case LH:                    (*P)->bf = EH;                    R->bf = RH;                    break;                case EH:                    (*P)->bf = R->bf = EH;                    break;                case RH:                    (*P)->bf = EH;                    R->bf = LH;                    break;            }            Rl->bf = EH;            avl_right_rotate(&(*P)->rchild);            avl_left_rotate(P);            break;    }}
下面,就是构建一棵AVL树的主函数:
/* * 创建一棵新AVL树及插入新节点。 * P为需创建或需插入节点的AVL树,e为插入元素,taller反映P是否”增高“ */status avl_node_insert(avl_node_t **P, int e, status *taller){    if (!(*P))    {        /* 空树,开始创建一颗新AVL树 */        *P = (avl_node_t *)malloc(sizeof(avl_node_t));        (*P)->data = e;        (*P)->lchild = (*P)->rchild = NULL;        (*P)->bf = EH;        *taller = TRUE;    }    else    {        if (e == (*P)->data)        {            /* AVL树中已存在内容为e的节点,直接返回,不作插入操作 */            *taller = FALSE;            return FALSE;        }                if (e < (*P)->data)        {            /* 在P的左子树中做插入操作 */            if (!avl_node_insert(&(*P)->lchild, e, taller)) // 未插入,则退出            {                return FALSE;            }                        if (*taller)      /* 已插入到P中左子树中,并且树“增高” */            {                switch ((*P)->bf)                {                    case LH:  // 原本左子树就比右子树高,需做左平衡操作                        avl_left_balance(P);                        *taller = FALSE;                        break;                    case EH:  // 原本左右子树等高                        (*P)->bf = LH;                        *taller = TRUE;                        break;                    case RH:  // 原本左子树比右子树低,现在则等高                        (*P)->bf = EH;                        *taller = FALSE;                        break;                }            }        }        else        {            /* 在P的右子树中做插入操作,原理同左子树 */            if (!avl_node_insert(&(*P)->rchild, e, taller))            {                return FALSE;            }                        if (*taller)            {                switch ((*P)->bf)                {                    case LH:                        (*P)->bf = EH;                        *taller = FALSE;                        break;                    case EH:                        (*P)->bf = RH;                        *taller = TRUE;                        break;                    case RH:                        avl_right_balance(P);                        *taller = FALSE;                        break;                }            }        }    }        return TRUE;}
测试函数,具体的打印函数我就不写了,略懒。。用gdb等调试工具跟一下,就知道没什么问题。我已经单步跟了,确保没有问题。
void main(){    int i;    int a[10] = {3, 2, 1, 4, 5, 6, 7, 10, 9, 8};    avl_node_t *P = NULL;    status taller;    for (i = 0; i < 10; i++)    {        avl_node_insert(&P, a[i], &taller);    }    printf("Root's data == %d !\n", P->data);}

总结

总结的话,就AVL树的内容而言没什么特别难的地方,就是每插入一个节点都需要确保保持自平衡。但就实现的算法而言,真正理解起来还是有点难度的,尤其是对于平衡因子的修改的那些代码,确实花了很多时间。

主要参考

《大话数据结构》

0 0
原创粉丝点击