二叉树之AVL树的平衡实现(递归与非递归)

这篇文章用来复习AVL的平衡操作,分别会介绍其旋转操作的递归与非递归实现,但是最终带有插入示例的版本会以递归呈现.

下面这张图绘制了需要旋转操作的8种情况.(我要给做这张图的兄弟一个赞)后面会给出这八种情况对应平衡实现.

[1]



情况1-2:

  这种需要旋转的结构一般称之为LL型,需要右旋 (顺时针旋转).

  我用一个图来抽象一下这两个情况,画的不好,我尽量表达吧.

  

  此时需要对A进行平衡操作,方法为:

  1.  将A的左子树换为B的右子树.
  2.   B的右子树换为A.
  •   非递归实现的代码为:
 1 void rotate_right(AVLTree &A){
 2
 3   AVLTree leftChild = A->left;
 4
 5   A->left = leftChild->right;
 6
 7   leftChild->right = A;
 8
 9   // 别忘了让父节点建立平衡后的连接
10
11   A = leftChild;
12
13 }

  非递归的操作在旋转前会充分考虑所有的旋转情况,目的是提早调整A下面各节点的高度.

  之后再进行旋转操作,这一点与递归的不同,可见递归是平衡完后再进行的高度调整.

  •   递归实现代码为:
 1 Position CAVLTree::singleRotateWithLeft(Position _K){
 2     Position K0;
 3     K0 = _K->left;
 4     _K->left = K0->right;
 5     K0->right = _K;
 6
 7     _K->Height = max(getHeight(_K->left),getHeight(_K->right)) + 1;
 8     K0->Height = max(getHeight(K0->left),getHeight(K0->right)) + 1;
 9
10     // 返回新的节点以替换
11     return K0;
12 }

情况3-4:

  这种需要旋转的结构一般称之为RR型,需要左旋(逆时针旋转).

  需要对A进行平衡操作,方法为:

  1.   将A的右子树换为B的左子树;
  2.   B的左子树换为A

  

  •   非递归的实现为:
void rotate_left(AVLTree &A){

    AVLTree rightChild = A->right;

    A->right = rightChild ->left;

    rightChild->left = A;

    A = rightChild;

}
  •   递归实现为:
Position CAVLTree::singleRotateWithRight(Position _K){
    Position K0;
    K0 = _K->right;
    _K->right = K0->left;
    K0->left = _K;

    _K->Height = max(getHeight(_K->left),getHeight(_K->right)) + 1;
    K0->Height = max(getHeight(K0->left),getHeight(K0->right)) + 1;
    return K0;
}

情况5-6: 

  这种需要旋转的结构一般称之为LR型,需要双旋转,即两次单旋.分别为左旋和右旋.

  需要对A进行平衡操作,方法为:

  1.   对B(A->left)做左旋
  2.   对A做右旋

  这个递归与非递归的方式都是一样的.

  •   非递归:
rotate_left(A->left);

rotate_right(A);
  •   递归:
Position CAVLTree::doubleRotateWithLeft(Position _K){
    _K->left = singleRotateWithRight(_K->left);
    return singleRotateWithLeft(_K);
}

  但是有没有一次性到位的方法呢?有的

  我把非递归的两个函数展开:

  发现最后一步都是确定与父节点的关系,并不是旋转中的具体过程,于是可以简化为这样:

AVLTree leftChild = A->left;

AVLTree leftRightChild = leftChild->left;

// 左旋

leftChild->right = leftRightChild->left;

leftRightChild->left = leftChild;

// 右旋

A->left = leftRightChild->right;

leftChild->right = A;

情况7-8:

  这种需要旋转的结构一般称之为RL型,需要双旋转,即两次单旋.分别为右旋和左旋.

  需要对A进行平衡操作,方法为:

  1.   对B进行右旋
  2.   对A进行左旋

  同样,递归与非递归版本是一样的.

  •   非递归:
rotate_right(A->left);

rotate_left(A);
  •   递归:
Position  CAVLTree::doubleRotateWithRight(Position _K){
    _K->right = singleRotateWithLeft(_K->right);

    return singleRotateWithRight(_K);
}

  同样,也有一次性到位的方法:

AVLTree rightChild = A->right;

AVLTree rightLeftChild = rightChild->left;

// 右旋

rightChild->left = rightLeftChild->right;

rightLeftChild->right = rightChild;

// 左旋

A->right = rightLeftChild->left;

rightLeftChild->left = A;


下面是实现部分:

0.结构声明[2]:

struct AvlNode;
typedef AvlNode * AvlTree;
typedef AvlNode * Position;

typedef int ELEMENT;

struct AvlNode
{
    AvlNode():data(),left(nullptr),right(nullptr),Height(){}
    ELEMENT data;
    AvlTree left;
    AvlTree right;
    int Height;
};

1.类中提供的API

class CAVLTree
{
public:
    CAVLTree(void);

    ~CAVLTree(void);

    size_t _insert_(ELEMENT &_data);

    int getTreeHeight();

    void showThisTree();

private:

    size_t size;

    AvlTree AvlTreeRoot;
private:

    Position insert_specific(ELEMENT &_data,AvlTree &_T);

    void showThisTree_specific(AvlTree _T);

    int getTreeHeight_specific(AvlTree _T);

    int max(int _a,int _b);

    int getHeight(Position _K);

    // 对于左左的分支,采用右旋
    Position singleRotateWithLeft(Position _K);

    //对于右右的分支,采用左旋
    Position singleRotateWithRight(Position _K);

    // 对于左右的分支,采用先左旋后右旋
    Position doubleRotateWithLeft(Position _K);

    // 对于右左的分支,采用先右旋后左旋
    Position  doubleRotateWithRight(Position _K);
};

2.获取高度:
  因为在max()函数获取结束后需要+1,所以这里的目的是将叶节点的Height想办法为0.

int CAVLTree::getHeight(Position _K){
    return (_K == nullptr )?-1:_K->Height;
}

3.插入操作:

  •   递归

  通过回溯的方式找到插入的位置,先平衡后调整高度;

  哈哈,有一个很有趣的细节为什么同时判断高度差一个是

  if(getHeight(_T->left) - getHeight(_T->right) == 2)

  而另一个是

  if (getHeight(_T->right) - getHeight(_T->left) == 2)

  因为这里已经知道了插入发生在哪边了,所以肯定是插入的那边会有破坏平衡的可能,不会造成尴尬的(小-大)的局面.

Position CAVLTree::insert_specific(ELEMENT &_data,AvlTree &_T){
    if (!_T)
    {
        _T = new AvlNode;
        _T->data = _data;
        _T->Height = 0;
        size++;
    }
    else if(_data < _T->data)
    {
        _T->left = insert_specific(_data,_T->left);
        if(getHeight(_T->left) - getHeight(_T->right) == 2)
        {
            // 根据新插入的节点所在位置来判断使用什么旋转
            if(_data < _T->left->data)
            {
                // 需要右旋
                _T = singleRotateWithLeft(_T);
            }
            else
            {
                // 需要先左旋后右旋
                _T = doubleRotateWithLeft(_T);
            }
        }
    }
    else if (_data > _T->data)
    {
        _T->right = insert_specific(_data,_T->right);
        if (getHeight(_T->right) - getHeight(_T->left) == 2)
        {
            if (_data > _T->right->data)
            {
                // 需要左旋
                _T = singleRotateWithRight(_T);
            }
            else
            {
                // 需要先右旋再左旋
                _T = doubleRotateWithRight(_T);
            }
        }
    }

    _T->Height = max(getHeight(_T->left) , getHeight(_T->right)) + 1 ;

    return _T;
}

  

  •   非递归[3]:

  可以发现,非递归的实现是先调整高度再平衡,但是要提前考虑所有情况.

  考虑左子树的情况:

void leftBalance(AVLNode* &t)
{
    AVLNode* lc = NULL;
    AVLNode* rd = NULL;
    lc = t->lchild;
    switch(lc->bf)
    {
        case LH:                    //顺时针旋转(即右旋)
            t->bf = EH;
            lc->bf = EH;
            R_Rotate(t);
            break;

        case EH:                    //删除节点时会发生,插入不会发生
            t->bf = LH;
            lc->bf = RH;
            R_Rotate(t);
            break;

        case RH:                    //先左旋后右旋
            rd = lc->rchild;
            switch(rd->bf)
            {
                case LH:
                    t->bf = RH;
                    lc->bf = EH;
                    break;
                case EH:
                    t->bf = EH;
                    lc->bf = EH;
                    break;
                case RH:
                    t->bf = EH;
                    lc->bf = LH;
                    break;
            }
            rd->bf = EH;
            L_Rotate(t->lchild);//不能写L_Rotate(lc);采用的是引用参数
            R_Rotate(t);
            break;
    }
}

  考虑右子树的情况:

void rightBalance(AVLNode* &t)
{
    AVLNode* rc = NULL;
    AVLNode *ld = NULL;

    rc = t->rchild;
    switch(rc->bf)
    {
    case RH:                //逆时针旋转(即左旋)
        t->bf = EH;
        rc->bf = EH;
        L_Rotate(t);
        break;
    case EH:                //删除节点时会发生,插入不会发生
        t->bf = RH;
        rc->bf = LH;
        L_Rotate(t);
        break;
    case LH:                //先右旋后左旋
        ld = rc->lchild;
        switch(ld->bf)
        {
        case LH:
            t->bf = EH;
            rc->bf = RH;
            break;
        case EH:
            t->bf = EH;
            rc->bf = EH;
            break;
        case RH:
            t->bf = LH;
            rc->bf = EH;
            break;
        }
        ld->bf = EH;
        R_Rotate(t->rchild);//不能写R_Rotate(rc);采用的是引用参数
        L_Rotate(t);
        break;
    }
}

 总结:

  递归真是神奇啊,对子树的处理递归的很漂亮,代码量是一方面,代码逻辑的清晰性也是非递归程序鲜有的.

  用这个来学习递归算法真是好工具,希望对于我后面复习图论有帮助.

  这篇文章中所述的非递归程序我并没有实现,肯定有疏忽的地方,欢迎大家指正.

完整示例中还有一个showThisTree(),它可以打印出漂亮的平衡二叉树.

相关代码请见我的github

[1]   AVL树的旋转操作 图解 最详细

[2] left 等价 leftChild,同理,right 也等价 rightChild.

[3]   平衡二叉树(AVL)的插入和删除详解(上)

[4]  参考教材 数据结构与算法分析:C语言描述(原书第2版)[美] MarkAllenWeiss 著

时间: 2024-10-09 22:00:55

二叉树之AVL树的平衡实现(递归与非递归)的相关文章

AVL树(平衡二叉查找树)

首先要说AVL树,我们就必须先说二叉查找树,先介绍二叉查找树的一些特性,然后我们再来说平衡树的一些特性,结合这些特性,然后来介绍AVL树. 一.二叉查找树 1.二叉树查找树的相关特征定义 二叉树查找树,又叫二叉搜索树,是一种有顺序有规律的树结构.它可以有以下几个特征来定义它: (1)首先它是一个二叉树,具备二叉树的所有特性,他可以有左右子节点(左右孩子),可以进行插入,删除,遍历等操作: (2)如果根节点有左子树,则左子树上的所有节点的值均小于根节点上的值,如果根节点有右子树,则有字数上的所有节

AVL树-自平衡二叉查找树(Java实现)

在计算机科学中,AVL树是最先发明的自平衡二叉查找树.AVL树得名于它的发明者 G.M. Adelson-Velsky 和 E.M. Landis,他们在 1962 年的论文 "An algorithm for the organization of information" 中发表了它. 一.AVL树的旋转规律 AVL树的基本操作一般涉及运做同在不平衡的二叉查找树所运做的同样的算法.但是要进行预先或随后做一次或多次所谓的"AVL旋转". 假设由于在二叉排序树上插入

python常用算法(5)——树,二叉树与AVL树

1,树 树是一种非常重要的非线性数据结构,直观的看,它是数据元素(在树中称为节点)按分支关系组织起来的结构,很像自然界中树那样.树结构在客观世界中广泛存在,如人类社会的族谱和各种社会组织机构都可用树形象表示.树在计算机领域中也得到了广泛应用,如在编译源程序时,可用树表示源程序的语法结构.又如在数据库系统中,树型结构也是信息的重要组织形式之一.一切具有层次关系的问题都可以用树来描述. 树(Tree)是元素的集合.树的定义是递归的,树是一种递归的数据结构.比如:目录结构.树是由n个结点组成的集合:如

Java数据结构系列之——树(4):二叉树的中序遍历的递归与非递归实现

package tree.binarytree; import java.util.Stack; /** * 二叉树的中序遍历:递归与非递归实现 * * @author wl * */ public class BiTreeInOrder { // 中序遍历的递归实现 public static void biTreeInOrderByRecursion(BiTreeNode root) { if (root == null) { return; } biTreeInOrderByRecursi

Java数据结构系列之——树(5):二叉树的后序遍历的递归与非递归实现

package tree.binarytree; import java.util.Stack; /** * 二叉树后序遍历的递归与非递归实现 * * @author wl * */ public class BitreePostOrder { // 后序遍历的递归实现 public static void biTreePostOrderByRecursion(BiTreeNode root) { if (root == null) { return; } biTreePostOrderByRe

树的递归与非递归遍历总结

树的递归遍历遍历很简单,非递归遍历要复杂一些,非递归先序.中序.后序遍历需要用一个辅助栈,而层次遍历则需要一个辅助队列. 树的结构: 1 public class Tree<T> { 2 private T data; 3 private Tree<T> left; 4 private Tree<T> right; 5 ... 6 } 用策略模式定义一个访问工具: 1 public interface Visitor<T> { 2 void process(

二叉树的前序、中序、后序遍历(递归、非递归)实现

本文部分来源于CSDN兰亭风雨大牛的原创.链接为http://blog.csdn.net/ns_code/article/details/12977901 二叉树是一种非常重要的数据结构,很多其他数据机构都是基于二叉树的基础演变过来的.二叉树有前.中.后三种遍历方式,因为树的本身就是用递归定义的,因此采用递归的方法实现三种遍历,不仅代码简洁且容易理解,但其开销也比较大,而若采用非递归方法实现三种遍历,则要用栈来模拟实现(递归也是用栈实现的).下面先简要介绍三种遍历方式的递归实现,再详细介绍三种遍

[转] 树的递归与非递归遍历

1 public class Tree<T> { 2 private T data; 3 private Tree<T> left; 4 private Tree<T> right; 5 6 private Tree(T data) { 7 this.data = data; 8 9 } 10 11 public Tree(T[] datas) { 12 makeTree(datas, this); 13 } 14 15 public interface Visitor

二叉树的建立、三种(递归、非递归)遍历方法

二叉树定义: 1.有且仅有一个特定的称之为根root的结点 2.当n>1时,除根结点之外的其余结点分为两个互不相交的子集.他们称为二叉树的左子树和右子树. 二叉树的一种建立方法: 若对有n个结点的完全二叉树进行顺序编号(1<=i<=n),那么,对于编号为i(i>=1)的结点. 当i=1时,该结点为根,它无双亲结点; 当i>1时,该节点的双亲编号为[i/2]; 若2i<=n,该结点为编号为2i的左孩子,否则没有左孩子 当2i+1<=n,该结点有编号为2i+1的右孩子