数据结构之 AVL树(平衡二叉树)(C语言实现)

来源:互联网 发布:mac os x 10.10.5 编辑:程序博客网 时间:2024/05/16 13:41

AVL树(平衡二叉树)

1. AVL树定义和性质

AVL(Adelson-Velskii和Landis发明者的首字母)树时带有平衡条件的二叉查找树

二叉查找树的性能分析:

  • 在一颗左右子树高度平衡情况下,最优的时间复杂度为O(log2n),这与这半查找相同;
  • 在一个只有右子树的二叉树中,最差的时间复杂度会脱变为线性查找O(n)。

二叉查找树的实现:二叉查找树的C语言实现

由于二叉查找树存在以上问题,所以AVL树通过旋转使自身始终保持平衡状态,因此,一颗基于二叉查找树的AVL树具有如下性质:

  1. 二叉查找树中任何一个结点的左子树和右子树高度相差的绝对值最多为1
  2. 它的左、右子树也分别都是平衡二叉树。

这里写图片描述

如果往上图左边的AVL树插入结点6,那么该树就会破坏结点8的平衡条件(如下图),为了恢复AVL树的性质,介绍修正的方法:旋转(rotation)

这里写图片描述

2. 旋转

当AVL树的平衡被破坏时,我们将必须重新平衡的结点称为α(刚才由于插入结点6而必须重新平衡的结点8)。这种不平衡出现在下面的四种情况:

  1. 对α的左儿子的左子树进行一次插入。
  2. 对α的左儿子的右子树进行一次插入。
  3. 对α的右儿子的左子树进行一次插入。
  4. 对α的右儿子的右子树进行一次插入。

上述四种情况,理论上只有两种情况,但是编程角度还是四种情形。

  • 情形1和4时关于α镜像对称(即左 - 左或右 - 右情况),因此该情况通过对树进行一次单旋转(single rotation)而完成调整;
  • 情形2和3时关于α镜像对称(即左 - 右或右 - 左情况),因此该情形通过双旋转(double rotation)来处理。

2.1单旋转

下图显示单旋转如何调整情况1(左 - 左情况)。虚线表示树的各层
这里写图片描述

如左图所示:k2节点不满足AVL的平衡条件,因为它的左子树比右子树深两层。

为了恢复平衡,就必须将k1子树整体上移作为根节点(因为k1子树深),k2子树整体下沉(因为k2子树浅),成为k1的右孩子。同时必须满足二叉查找树的左子树必须小于右子树的性质,将Y节点作为k2的左孩子。至此,单旋转完成调整,X,Y,Z处于同一层。

将之前插入节点6而造成节点8不平衡的情况,通过单旋转实现平衡(通过改变结点的孩子指针)。如下图:

这里写图片描述

由于情形4(右 - 右)与情况1(左 - 左)时镜像对称的,因此,和理解上述单旋转修复情况1的方法类似。

这里写图片描述

k1结点不满足平衡条件。将k2结点上提,k1结点下沉,同时满足左孩子小于右孩子的性质,即可完成修复情况4。

接下来,演示一个长一点的单旋转的例子:

  • 初始的空AVL树插入关键字3、2和1,此时根节点已经被破坏平衡,属于(左 - 左)情况,因此通过单旋转调整。

这里写图片描述

  • 继续插入4和5,插入5之后,结点3又被破坏平衡,属于(右 - 右)情况,通过单旋转调整。

这里写图片描述

  • 继续插入6,根节点的平衡被破坏,属于(右 - 右)情况,通古单旋转调整。

这里写图片描述

  • 继续插入7,结点5平衡被破坏,属于(右 - 右)情况,通过单旋转调整。

这里写图片描述

2.2双旋转

单旋转可以调整情况1和4,但是对于2和3,并不能修复AVL树的性质(如下图),k1的左右子树的深度仍然为2。

这里写图片描述

2和3情况是(左 - 右)和(右 - 左)的情形,因此通过双旋转来修复AVL树的性质。

情形2:对α的 左儿子的右子树 进行一次插入。情形3:对α的 右儿子的左子树 进行一次插入。

通过双旋转修复情形2,如下图所示:

这里写图片描述

如图所示,第一次,先将k1作为根节点,以k1、k2和C为路径,以(右 - 右)方式通过单旋转调整,第二次,以k3为根节点,以k3、k2和k1为路径,以(左 - 左)方式 通过单旋转调整如右上图所示。将最深的节点,提高到根节点,以达到平衡的性质。

因此,一次双旋转可以看成两次单旋转。

我们在刚才的例子上继续插入节点,直观的感受双旋转。继续插入16和15结点,此时7的平衡被破坏。

这里写图片描述

此时的情形是(右 - 左)类型。通过一次双旋转,将最深的15旋转到了被破坏平衡的节点位置。继续插入14,6的平衡又被破坏。

这里写图片描述
此时属于(右 - 左)类型。通过一次双旋转,将7旋转到了被破坏平衡的节点位置。继续插入13,导致根节点4平衡被破坏。

这里写图片描述

此时属于(右 - 右)类型,通过一次单旋转,将7节点旋转到根节点,恢复平衡的性质。继续插入12,节点15平衡被破坏。

这里写图片描述

此时属于(左 - 左)类型。通过一侧单旋转。恢复平衡性质。继续插入11,还需要进行一个单旋转,对其后的10的插入也需要单旋转,然后插入8不需要旋转,这样就建立了一颗近乎理想的平衡树。

这里写图片描述
最后插入9,需要进行一次双旋转,就能得到下面的树。

这里写图片描述

根据以上过程,就可以得到编程的细节

  • 插入:为了将关键字是X的新节点插入到一颗AVL树中,递归的将X插入到T的相应的子树中。如果子树高度不变,则插入完成,如果在T中出现高度不平衡,那么根据X和子树中的关键字做适当的单旋转或双旋转,跟新这些高度,从而完成插入。
  • 删除:删除则要比插入更加复杂,递归寻找要删除的关键字,分有一个或零个孩子和有两个孩子的情况,分别去删除,每次删除完都要判断是否平衡,不平衡,通过旋转修正。

AVL树的实现

  • AVLtree.h文件
#ifndef _SEARCHTREE_H_#define _SEARCHTREE_H_#define MAX(a, b)   (a > b ? a : b)#define GET_HEIGHT(T)   (T == NULL ? -1 : T->height)    //获得树的高度typedef int myType;typedef struct treeNode {    myType element;    struct treeNode *lchild;    struct treeNode *rchild;    int height;     //存储树的高度信息}AVLtree;void preOrder(AVLtree *T);void inOrder(AVLtree *T);void postOrder(AVLtree *T);void levelOrder(AVLtree *T);AVLtree *find(myType data, AVLtree *T);AVLtree *findMin(AVLtree *T);AVLtree *findMax(AVLtree *T);AVLtree *insert(myType data, AVLtree *T);AVLtree *erase(myType data, AVLtree *T);#endif
  • AVLtree.c文件
#include <stdio.h>#include <stdlib.h>#include "AVLtree.h"#include "queue.h"      //水平遍历时需要队列数据结构AVLtree *find(myType data, AVLtree *T){    if(T == NULL)         return NULL;    if(data < T->element)        return find(data, T->lchild);    else if(data > T->element)        return find(data, T->rchild);    else         return T;}AVLtree *findMin(AVLtree *T){    if(T == NULL)         return NULL;    else if(T->lchild == NULL)        return T;    else         return findMin(T->lchild);}AVLtree *findMax(AVLtree *T){    if(T != NULL)         while(T->rchild != NULL)            T = T->rchild;    return T;}static int getHeight(AVLtree *T){    return GET_HEIGHT(T);}static AVLtree *singleRotateWithLeft(AVLtree *T){    AVLtree *TT;    TT = T->lchild;    T->lchild = TT->rchild;    TT->rchild = T;    T->height = MAX(getHeight(TT->lchild), getHeight(T->rchild)) + 1;    TT->height = MAX(getHeight(TT->lchild), T->height) + 1;    return TT;}static AVLtree *singleRotateWithRight(AVLtree *T){    AVLtree *TT;    TT = T->rchild;    T->rchild = TT->lchild;    TT->lchild = T;    T->height = MAX(getHeight(TT->lchild), getHeight(T->rchild)) + 1;    TT->height = MAX(getHeight(TT->lchild), T->height) + 1;    return TT;}static AVLtree *doubleRotateWithLeft(AVLtree *T){    T->lchild = singleRotateWithRight(T->lchild);    return singleRotateWithLeft(T);}static AVLtree *doubleRotateWithRight(AVLtree *T){    T->rchild = singleRotateWithLeft(T->rchild);    return singleRotateWithRight(T);}AVLtree *insert(myType data, AVLtree *T){    if(T == NULL) {        T = (AVLtree *)malloc(sizeof(struct treeNode));        T->element = data;        T->lchild = NULL;        T->rchild = NULL;        T->height = 0;    } else if (data < T->element) {        T->lchild = insert(data, T->lchild);            if(GET_HEIGHT(T->lchild) - GET_HEIGHT(T->rchild) == 2)            if(data < T->lchild->element)                T = singleRotateWithLeft(T);            else                 T = doubleRotateWithLeft(T);    } else if (data > T->element) {        T->rchild = insert(data, T->rchild);            if(GET_HEIGHT(T->rchild) - GET_HEIGHT(T->lchild) == 2)            if(data > T->rchild->element)                T = singleRotateWithRight(T);            else                 T = doubleRotateWithRight(T);    }    T->height = MAX(getHeight(T->lchild), getHeight(T->rchild) + 1);    return T;}AVLtree *erase(myType data, AVLtree *T){    AVLtree *tmpNode;    if(T == NULL) {        printf("NOT FOUNT\n");    } else if (data < T->element) {        T->lchild = erase(data, T->lchild);         if(getHeight(T->rchild) - getHeight(T->lchild) == 2) {            AVLtree *tmp = T->rchild;            if(getHeight(tmp->lchild) > getHeight(tmp->rchild))                T = doubleRotateWithRight(T);            else                T = singleRotateWithRight(T);        }    } else if (data > T->element) {        T->rchild = erase(data, T->rchild);        if(getHeight(T->lchild) - getHeight(T->rchild) == 2) {            AVLtree *tmp = T->lchild;            if(getHeight(tmp->rchild) > getHeight(tmp->lchild))                T = doubleRotateWithLeft(T);            else                T = singleRotateWithLeft(T);        }    //found element to delete, two children    } else if (T->lchild && T->rchild){        if(getHeight(T->rchild) > getHeight(T->lchild)) {            tmpNode = findMin(T->rchild);//将右子树的最小值代替root            T->element = tmpNode->element;            T->rchild = erase(T->element, T->rchild);        } else {            tmpNode = findMax(T->lchild);//将左子树的最大值代替root               T->element = tmpNode->element;            T->lchild = erase(T->element, T->lchild);        }    } else {        //one or zero children        tmpNode = T;            T = (T->lchild == NULL ? T->rchild : T->lchild);        free(tmpNode);    }       return T;}void levelOrder(AVLtree *T){    QUEUE *q = createQueue(100);    while(T != NULL) {        printf("%d ", T->element);          if(T->lchild != NULL)             enQueue(T->lchild, q);          if(T->rchild != NULL)            enQueue(T->rchild, q);        if(!isEmpty(q))            T = frontAndDequeue(q);        else            T = NULL;    }    disposeQueue(q);}void preOrder(AVLtree *T){    if(T != NULL) {        printf("%d ", T->element);          preOrder(T->lchild);        preOrder(T->rchild);    }}void inOrder(AVLtree *T){    if(T != NULL) {        inOrder(T->lchild);         printf("%d ", T->element);          inOrder(T->rchild);    }}void postOrder(AVLtree *T){    if(T != NULL) {        postOrder(T->lchild);           postOrder(T->rchild);        printf("%d ", T->element);      }}

代码放在github:AVLtree实现

本文参考:《数据结构与算法分析:C语言实现》——Mark Allen Weiss (维斯)

0 0