AVL平衡二叉搜索树

作者: _shen | 来源:发表于2018-03-22 10:08 被阅读15次

AVL定义

AVL树是根据它的发明者G.M. Adelson-Velsky和E.M. Landis命名的。相比于“二叉搜索树”,它的特点是:AVL树中任何节点的两个子树的高度差小于等于1。

高度定义:树的高度为树的最大层次。即空的二叉树的高度是0,非空树的高度等于它的最大层次。如下图所示,图中的H表示树的高度:

根据AVL树的定义,任何节点的两个子树的高度差要小于等于1,我们逐个进行判断。首先是关键字为10的节点,左子树高度为2,右子树高度为1,高度差等于H-H=1,因此符合,类似地,我们对其余节点进行判断,发现均符合高度差小于等于1的要求,因此这是一棵AVL树。下面我们看一棵不符合要求的树,关键字为10的节点,左子树高度为3,右子树高度为1,H-H=2,不符合AVL要求,因此这不是一个AVL树。那么问题来了,我们如何建立一棵AVL树?因为树的建立是逐个插入节点的过程,所以要确保一个树始终是AVL树,我们需要在每次插入或删除一个节点后进行检查和调整。

把上面两棵树做对比,我们发现:新插入一个关键字为2的节点后,导致AVL树失去了平衡,失衡的节点在关键字为10节点的地方,因此我们需要在失衡的节点处下功夫。从图中来看,关键字1节点的插入导致关键字10的节点左侧太重,那么只要我们将整棵树的重心往左挪一点,就可以平衡。移到什么地方呢?因为新插入一个子节点,树的高度增加量不会超过2,要么是1要么是0,所以我们只需要将重心移至左子树的位置,那么就可以解决问题。

既然要移至5节点,那么10节点必然要作为5节点的一个子树,因为是左子树太重,重心向左挪,那么10节点必然是作为5节点的右子树,5节点原来的右子树便成为了10节点的左子树。如下图所示:

上述分析中我们知道,5节点提高一个层次后,其附属的节点层次均提高一个层次,因此可以平衡(抵消)了之前因为1节点插入导致的失衡。但是5节点的右子树并没有随着5节点提升一个层次,它只是平移至10节点的左子树的位置。这里存在个问题,如果,新插入的节点在这棵被平移至10节点左子树位置的分支上,那么失衡问题并不会得到解决,如下图所示:

新插入6节点之后导致10节点失衡,重心左移,由于6节点处于5节点右子树中,因此5节点的右子树平移至10节点左子树位置后,并没有抵消因为节点6带来的高度增加,因此下图中的这棵仍然是不平衡的。

因此,我们就需要想个办法,抵消因为插入节点6带来的高度增加。前面我们已经分析过了,只有处于5节点的左子树位置才会在移动重心后提升一个层次。那么,我们需要将5节点子树的重心右移,这样一来,10节点的子树移动重心后,刚好可以抵消这部分力量,如下图所示:

然后,对10节点的子树向左移动重心,整个树处于平衡。如下图:

类似地,如果失衡发生在某节点的右子树,也需要进行如上述的处理。总共可分为四种情况。

①LL:Left Left,也称为“左左”。插入或删除一个节点后,根节点左子树的左子树还有非空子节点,导致“根的左子树的高度”比“根的右子树的高度”大2,导致AVL树失去了平衡。

②LR:Left Right,也称为“左右”。插入或删除一个节点后,根节点的左子树的右子树还有非空子节点,导致“根的左子树的高度”比“根的右子树的高度”大2,导致AVL树失去了平衡。

③RR:Right Right,也称为“右右”。插入或删除一个节点后,根节点的右子树的右子树还有非空子节点,导致"根的右子树的高度"比"根的左子树的高度"大2,导致AVL树失去了平衡。

④RL:Right Left,也称为“右左”。插入或删除一个节点后,根节点的右子树的左子树还有非空子节点,导致“根的右子树的高度”比“根的左子树的高度”大2,导致AVL树失去了平衡。

首先,我们定义AVL树节点结构。

typedef struct AVLTreeNode
{
    Type key;
    int height;
    struct AVLTreeNode *left;
    struct AVLTreeNode *right;
} AVLTreeNode, *pAVLTreeNode;

①针对LL情况的调平:

/**
 * LL调平
 * n0 : 失衡的节点
 * return : 调平后的根节点
 */
static pAVLTreeNode left_left_rotation( pAVLTreeNode n0 )
{
    pAVLTreeNode n1;

    n1 = n0->left;
    n0->left = n1->right;
    n1->right = n0;

    n0->height = MAX( HEIGHT(n0->left), HEIGHT(n0->right) );
    n1->height = MAX( HEIGHT(n1->left), HEIGHT(n1->right) );

    return n1;
}

②针对RR情况的调平:

/**
 * RR调平
 * n0 : 失衡的节点
 * return : 调平后的根节点
 */
static pAVLTreeNode right_right_rotation( pAVLTreeNode n0 )
{
    pAVLTreeNode n1;

    n1 = n0->right;
    n0->right = n1->left;
    n1->left = n0;

    n0->height = MAX( HEIGHT(n0->left), HEIGHT(n0->right) );
    n1->height = MAX( HEIGHT(n1->left), HEIGHT(n1->right) );

    return n1;
}

③针对LR情况的调平:

/**
 * LR调平
 * n0 : 失衡的节点
 * return : 调平后的根节点
 */
static pAVLTreeNode left_right_rotation( pAVLTreeNode n0 )
{
    pAVLTreeNode n1;

    n0->left = right_right_rotation( n0->left );
    n1 = left_left_rotation( n0 );

    return n1;
}

④针对RL情况的调平:

/**
 * RL调平
 * n0 : 失衡的节点
 * return : 调平后的根节点
 */
static pAVLTreeNode right_left_rotation( pAVLTreeNode n0 )
{
    pAVLTreeNode n1;

    n0->left = left_left_rotation( n0->left );
    n1 = right_right_rotation( n0 );

    return n1;
}

此外,还有上面用到的两个宏:

#define HEIGHT(P)  ( (P==NULL) ? 0 : (((Node * )(P))->height) )
#define MAX(LEFT, RIGHT)  ( (LEFT) > (RIGHT) ? (LEFT) : (RIGHT) )

那么,现在我们看看在插入和删除节点的过程中如何实现AVL树的调平。

我们知道,每次节点发生变化后,从根部到此节点相关联的节点高度都会发生变动,因此,我们可以采取递归的方式,先递归到插入或删除的位置,然后进行调平,接着在回溯的过程中不断更新与之关联的节点高度。

AVL基本操作

插入节点
/**
 * 插入节点
 * tree : 失衡的节点
 * key : 插入节点的key,整棵树唯一,不能有重复
 * return : 调平后的根节点
 */
pAVLTreeNode static insert_avltree( pAVLTreeNode tree, Type key )
{
    if ( tree == null ) {
        tree = (pAVLTreeNode)malloc(sizeof(AVLTreeNode));

        if( tree == NULL ) {
            printf("分配节点失败!\r\n");
            return NULL;
        }

        tree->height = 0;
        tree->left = NULL;
        tree->right = NULL;
    } else if (key < tree->key) {
        // 递归左子树
        tree->left = insert_avltree( tree->left, key );
        // 检查是否调平
        if ( HEIGHT(tree->left) - HEIGHT(tree->right) == 2 ) {
            if ( key < tree->left->key ) {
                // LL
                tree = left_left_rotation(tree);
            } else {
                // LR
                tree = left_right_rotation(tree);
            }
        }

    } else if (key > tree->key) {
        // 递归右子树
        tree->right = insert_avltree( tree->right, key );
        // 检查是否调平
        if ( HEIGHT(tree->right) - HEIGHT(tree->left) == 2 ) {
            if ( key > tree->right->key ==NULL ) {
                // RR
                tree = right_right_rotation(tree);
            } else {
                // RL
                tree = right_left_rotation(tree);
            }
        }

    } else {
        // key == tree->key invalid
        printf("不能插入key重复的节点!\r\n");
        return NULL;
    }

    // 调平后,更新节点高度
    tree->height = MAX(tree->left, tree->right);

    return tree;
}
删除节点
/**
 * 删除节点
 * tree : 失衡的节点
 * key : 要删除节点的key,整棵树唯一,不能有重复
 * return : 调平后的根节点
 */
pAVLTreeNode static delete_avltree( pAVLTreeNode tree, Type key )
{
    if ( tree == NULL ) {
        return NULL;    
    } else if ( key < tree->key ) {
        // 递归左子树
        tree = delete_avltree( tree->left, key );
        // 检查是否需要调平
        if ( HEIGHT(tree->left) - HEIGHT(tree->right) == 2 ) {
            // LL
            tree = left_left_rotation(tree);
            // LR
            tree = left_right_rotation(tree);
        }
    } else if ( key > tree->key ) {
        // 递归右子树
        tree = delete_avltree( tree->right, key );
        // 检查是否需要调平
        if ( HEIGHT(tree->right) - HEIGHT(tree->left) == 2 ) {
            // RR
            tree = right_right_rotation(tree);
            // RL
            tree = right_left_rotation(tree);
        }
    } else {
        // 就是要删除的节点
        if ( tree->left && tree->right ) {
            // 节点的左右子树都存在,需要从左右子节点挑选出高度最高的那棵子树
            // 替换删除的节点
            if ( HEIGHT(tree->left) > HEIGHT(tree->right) ) {
                // 左子树比右子树高,从左子树中找出最大的那个节点进行替换
                pAVLTreeNode max = maxnode_avltree( tree->left );
                tree->key = max->key;
                // 删除左子树中最大的那个节点
                tree = delete_avltree(tree->left);
            } else {
                // 左子树比右子树矮,从右子树中找出最小的那个节点进行替换
                pAVLTreeNode min = minnode_avltree( tree->right );
                tree->key = min->key;
                // 删除左子树中最大的那个节点
                tree = delete_avltree(tree->right);
            }
        } else {
            // 只有一个孩子或者没有
            pAVLTreeNode pTemp = tree;
            tree = tree->left ? tree->left : tree->right;
            // 销毁节点
            free(pTemp);
        }
    }

    // 修正根节点的高度
    tree->height = MAX( HEIGHT(tree->left), HEIGHT(tree->right) );

    return tree;
}
查找最小节点
/**
 * 查找最小节点
 * return : 最小节点
 */
static pAVLTreeNode minnode_avltree( pAVLTreeNode tree )
{
    if ( tree == NULL ) return NULL;

    while ( tree->left != NULL ){
        tree = tree->left;
    }

    return tree;
}
查找最大节点
/**
 * 查找最大节点
 * return : 最大节点
 */
static pAVLTreeNode minnode_avltree( pAVLTreeNode tree )
{
    if ( tree == NULL ) return NULL;

    while ( tree->right != NULL ){
        tree = tree->right;
    }

    return tree;
}

觉得有用就点个赞!

相关文章

网友评论

本文标题:AVL平衡二叉搜索树

本文链接:https://www.haomeiwen.com/subject/zswoqftx.html