java数据结构与算法之平衡二叉树(AVL树)的设计与实现

【版权申明】未经博主同意,不允许转载!(请尊重原创,博主保留追究权)
http://blog.csdn.net/javazejian/article/details/53892797
出自【zejian的博客】

关联文章:

java数据结构与算法之顺序表与链表设计与实现分析
java数据结构与算法之双链表设计与实现
java数据结构与算法之改良顺序表与双链表类似ArrayList和LinkedList(带Iterator迭代器与fast-fail机制)
java数据结构与算法之栈(Stack)设计与实现
java数据结构与算法之队列(Queue)设计与实现
java数据结构与算法之递归思维(让我们更通俗地理解递归)
java数据结构与算法之树基本概念及二叉树(BinaryTree)的设计与实现
java数据结构与算法之平衡二叉查找树(AVL树)的设计与实现

运行代码做过修改,建议看文章尾部提供的github代码,本篇文章代码暂未更新!

  上一篇博文中,我们详细地分析了树的基本概念以及二叉查找树的实现过程,基于二叉查找树的特性,即对于树种的每个结点T(T可能是父结点),它的左子树中所有项的值小T中的值,而它的右子树中所有项的值都大于T中的值。这意味着该树所有的元素可以用某种规则进行排序(取决于Comparable接口的实现),以致于二叉树查找树能够胜任快速地查找过程,这个查找的过程的时间复杂度为O(logN),但是这个时间复杂度并不是严格意义上的O(logN),在某些情况下还是会上升到O(N),显然这并不是一件好事情,因此本篇我们将来讨论另一种更为稳定的二叉树,它就是AVL树。以上是本篇将会讨论的主要内容:

文章目录

#普通二叉查找树的问题
  在开篇,我们提到过,普通二叉树(二叉查找树)在操作的时间复杂度上不一定遵循O(㏒n),也有可能是O(n),这是为什么呢?在上一篇中,我们明明插入都按照一定规则比较的呀,其实那是因为我们在上篇测试时执行了随机插入的操作,如果此时利用上篇实现的二叉搜索树将一段已排序好的数据一个个插入后,就会发现如下情况了:

  从图中我们可以发现,把已排序的1-9数据进行正序和倒序插入后,树的结构已变成单向左子树或者右子树了,如果我们在往里插入已排序的数据,那么单向左子树或者右子树越来越长,此时已跟单链表没有什么区别了,因此对此结构的操作时间复杂度自然就由O(㏒n)变成O(n)了,这也就是普通二叉查找树不是严格意义上O(㏒n)的原因。那么该如何解决这个问题呢?事实上一种解决的办法就是要有一个称为平衡的附加结构条件即:任何结点的深度不得过深,而这种数据结构就是我们本篇要分析的平衡二叉树(AVL),它本身也是一种二叉查找树,只不过不会出现前面我们分析的情形罢了,接下来我们就来分析一下这棵平衡二叉树。
#平衡二叉树的定义
  通过上面的分析,我们明白的普通二叉查找树的不足,也知道了如何去解决这个缺点,即构建树时要求任何结点的深度不得过深(子树高度相差不超过1),而最终这棵树就是平衡二叉树(Balanced Binary Tree),它是G.M. Adelson-Velsky 和 E.M. Landis在1962年在论文中发表的,因此又叫AVL树。这里我们还需要明确一个概念,AVL树只是实现平衡二叉树的一种方法,它还有很多的其他实现方法如红黑树、替罪羊树、Treap、伸展树等,后面我们还会分析其他树的实现。ok~,接着来了解一下AVL树的特性:一棵AVL树是其每个结点的左子树和右子树的高度最多相差1的二叉查找树(空树的高度为-1),这个差值也称为平衡因子(其取值可以是1,0,-1,平衡因子是某个结点左右子树层数的差值,有的书上定义是左边减去右边,有的书上定义是右边减去左边,这样可能会有正负的区别,但是这个并不影响我们对平衡二叉树的讨论)。如下图

  图(1)显然就是一棵平衡二叉树,它每个结点的左子树和右子树的高度最多相差1,同时也是一棵二叉查找树,而图二虽然也是一棵二叉查找树,但是它每个结点的左子树和右子树的高度相差却到达了2,因此不是平衡二叉树。理解了平衡二叉树的概念后,我们在思考一下,那些操作可能引起平衡发生变化呢?显然只有那些引起结点数量变化的操作才可能导致平衡被改变,也就是删除和插入操作了,如下图,我们把6插入到图a后,结构变成了图b,这时原本的平衡二叉树就失去平衡了。

  显然图b已失去平衡,如果发生这样的情况,我们就必须考虑插入元素后恢复二叉树的平衡性质,实际上也总是可以通过对树进行简单的修复来让其重新恢复到平衡,而这样的简单操作我们就称之为旋转,当然旋转也有单旋转和双旋转之分,下面我们将会一一分析,这里有点需要明白的是,无论是插入还是删除,只有那些从插入或者删除点到根结点的路径上的结点的平衡才有可能被改变,因为只有这些结点的子树才可能发生变化,所以最终也只需针对这些点进行平衡修复操作即可。
#平衡二叉树的设计与实现
  ok~,有了旋转的概念后,我们接着了解如何通过旋转来修复一棵失衡的二叉树,这里假设结点X是失衡点,它必须重新恢复平衡,由于任意结点的孩子结点最多有两个,而且导致失衡的必要条件是X结点的两棵子树高度差为2(大于1),因此一般只有以下4种情况可能导致X点失去平衡:
① 在结点X的左孩子结点的左子树中插入元素
② 在结点X的左孩子结点的右子树中插入元素
③ 在结点X的右孩子结点的左子树中插入元素
④ 在结点X的右孩子结点的右子树中插入元素
以上4种情况,其中第①情况和第④情况是对称的,可以通过单旋转来解决,而第②种情况和第③情况是对称的,需要双旋转来解决。在分析这四种情况前,我们先看看AVL的结点该如何设计的,其声明如下:

package com.zejian.structures.Tree.AVLTree;

/**
 * Created by zejian on 2016/12/25.
 * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
 * 平衡二叉搜索树(AVL树)节点
 */
public class AVLNode<T extends Comparable> {

    public AVLNode<T> left;//左结点

    public AVLNode<T> right;//右结点

    public T data;

    public int height;//当前结点的高度

    public AVLNode(T data) {
        this(null,null,data);
    }

    public AVLNode(AVLNode<T> left, AVLNode<T> right, T data) {
        this(left,right,data,0);
    }

    public AVLNode(AVLNode<T> left, AVLNode<T> right, T data, int height) {
        this.left=left;
        this.right=right;
        this.data=data;
        this.height = height;
    }
}

  可以看出,为了满足平衡二叉树的特性,需要在原来的二叉搜索树(BST)的结点中添加一个height的字段表示高度,方便我们计算,这里强调一下,高度和深度一组相反的概念,高度是指当前结点到叶子结点的最长路径,如所有叶子结点的高度都为0,而深度则是指从根结点到当前结点的最大路径,如根结点的深度为0。这里约定空结点(空子树)的高度为-1,叶子结点的高度为0,非叶子节点的高度则根据其子树的高度而计算获取,如下图:

ok~,了解上述的内容,下面就来分析4种可能失衡的情景。
##平衡二叉树的单旋转算法与实现
###左左单旋转(LL)情景①分析
  从下图可以看出,结点X并不能满足AVL树的性质,因为它的左子树比右子树深2层,这种情况就是典型的LL情景,此时需要通过右向旋转来修复失衡的树,如图1,X经过右旋转后变成图2,W变为根结点,X变为W的右子树,同时W的右子树变为X的左子树,树又重新回到平衡,各个结点的子树高度差都已在正常范围。一般情况下,我们把X结点称为失衡点,修复一棵被破坏的AVL树时,找到失衡点是很重要的并把通过一次旋转即可修复平衡的操作叫做单旋转,从图3和图4可知,在原始AVL树插入7结点后,结点9变为失衡点,树再满足AVL性质,因此需要对9结点进行左左单旋转(即向右旋转)后,得到图4,我们发现此时并没有操作树的根结点(6),实际上这是因为正常情况下,不必从树的根结点进行旋转,而是从插入结点处开始,向上遍历树,并更新和修复在这个路径上的每个结点的平衡及其平衡信息(高度)即可。

其代码实现如下,比较简单:

/**
 * 左左单旋转(LL旋转) w变为x的根结点, x变为w的右子树
 * @param x
 * @return
 */
private AVLNode<T> singleRotateLeft(AVLNode<T> x){
    //把w结点旋转为根结点
    AVLNode<T> w=  x.left;
    //同时w的右子树变为x的左子树
    x.left=w.right;
    //x变为w的右子树
    w.right=x;
    //重新计算x/w的高度
    x.height=Math.max(height(x.left),height(x.right))+1;
    w.height=Math.max(height(w.left),x.height)+1;
    return w;//返回新的根结点
}

###右右单旋转(RR)情景④分析

  接着再来看看右右单旋转(RR)的情景,如下图,可以发现与左左单旋转的情况恰好是一种镜像关系,同样结点X并不能满足AVL树的性质,在这样的情景下,需要对X结点进行左旋转来修复树的平衡,如图1经左旋转后变了图2,此时X变为了根结点,W变为X的左孩子,X的左子树变为W的右子树,而树又重新恢复了平衡。如图3和图4的实例情景,原始的AVL树在12处插入结点18后,结点10就变成了失衡点,因为10的左子树和右子树的高度相差2,显然不符合AVL树性质,需要对结点10进行右右单旋转修复(向左旋转),然后得到图4,此时树重新回到了平衡,这便是右右单旋转(RR)的修复情景。

代码实现如下:

/**
 * 右右单旋转(RR旋转) x变为w的根结点, w变为x的左子树
 * @return
 */
private AVLNode<T> singleRotateRight(AVLNode<T> w){

    AVLNode<T> x=w.right;

    w.right=x.left;
    x.left=w;

    //重新计算x/w的高度
    w.height=Math.max(height(w.left),height(w.right))+1;
    x.height=Math.max(height(x.left),w.height)+1;

    //返回新的根结点
    return x;
}

##平衡二叉树的双旋转算法与实现

  前面两种情景都已分析完,它们都是基于单旋转的算法,但这种算法存在一个问题,那就是对情景②③无法生效,根本问题在于子树Y太深了,如下图所示:

  显然经过一次单旋转的修复后无论是X或者W作为根结点都无法符合AVL树的性质,此时就需要用双旋转算法来实现了。由于子树Y是在插入某个结点后导致X结点的左右子树失去平衡,那么就说明子树Y肯定是非空的,因此为了易于理解,我们可以把子树Y看作一个根结点和两棵子树,如下图所示:

  ok~,明白了单旋转对于情景②③的窘境,下面我们就通过双旋转算法来解开这个窘境。
###左右双旋转(LR)情景②分析
  为了重新平衡,通过上述的分析显然不能把X根结点,而X与W间的旋转也解决不了问题,那唯一的旋转就是把Y作为新根。这样的话,X、W就不得不成为Y的孩子结点,其中W作为Y的左孩子结点,而X成为Y的右孩子结点。这里我们以下图为例来分析,为了达到以上结果,需要W、Y进行单旋转(图1),这里我们可把WY组成的子树看成前面的右右旋转情景,然后进行左向旋转,得到图2,W变为Y的左子树同时Y的左子树B变成W的右子树,其他不变,到此第一次旋转完成,进行第二次旋转,以X结点向右进行旋转(同样可看作左左情景),由图2得到图3,X变成Y的右孩子结点并且Y的右子树C变成X的左子树,第二次旋转完成,树也重新恢复到平衡。

  在左右双旋转实例图123中,在原AVL树种插入结点7后,结点8变成了失衡点,此时需要把6结点变为根结点才能重新恢复平衡。因此先进行左向旋转再进行右向旋转,最后树恢复平衡。算法代码实现如下:

/**
 * 左右旋转(LR旋转) x(根) w y 结点 把y变成根结点
 * @return
 */
private AVLNode<T> doubleRotateWithLeft(AVLNode<T> x){
    //w先进行RR旋转
    x.left=singleRotateRight(x.left);
    //再进行x的LL旋转
    return singleRotateLeft(x);
}

###右左双旋转(RL)情景③分析
  对于右左双旋转(RL)情景和左右双旋转(LR)情景是一对镜像,旋转的原理上一样的,这里就不废话了,给出下图协助理解即可(已很清晰了):

实现代码如下:

/**
 * 右左旋转(RL旋转)
 * @param w
 * @return
 */
private AVLNode<T> doubleRotateWithRight(AVLNode<T> x){
    //先进行LL旋转
    x.right=singleRotateLeft(x.right);
    //再进行RR旋转
    return singleRotateRight(x);
}

  好~,到此4种情况都已分析完毕,接着我们就利用这种四种情况来重写AVL树的插入操作过程。

##平衡二叉树插入操作的实现
  实际上,有了上述四种情况后,编写插入操作的编码细节并不会太困难,这里我们给出主要思路和代码实现即可(很清晰的注释),与BST(二叉查找树)的插入实现原理一样,使用递归算法,根据值大小查找到插入位置,然后进行插入操作,插入完成后,我们需要进行平衡判断,评估子树是否需要进行平衡修复,需要则利用上述的四种情景套入代码即可,最后要记得重新计算插入结点路径上的高度。代码实现如下:

/**
* 插入方法
* @param data
*/
@Override
public void insert(T data) {
   if (data==null){
       throw new RuntimeException("data can\'t not be null ");
   }
   this.root=insert(data,root);
}

private AVLNode<T> insert(T data , AVLNode<T> p){

   //说明已没有孩子结点,可以创建新结点插入了.
   if(p==null){
       p=new AVLNode<T>(data);
   }else if(data.compareTo(p.data)<0){//向左子树寻找插入位置
       p.left=insert(data,p.left);

       //插入后计算子树的高度,等于2则需要重新恢复平衡,由于是左边插入,左子树的高度肯定大于等于右子树的高度
       if(height(p.left)-height(p.right)==2){
           //判断data是插入点的左孩子还是右孩子
           if(data.compareTo(p.left.data)<0){
               //进行LL旋转
               p=singleRotateLeft(p);
           }else {
               //进行左右旋转
               p=doubleRotateWithLeft(p);
           }
       }
   }else if (data.compareTo(p.data)>0){//向右子树寻找插入位置
       p.right=insert(data,p.right);

       if(height(p.right)-height(p.left)==2){
           if (data.compareTo(p.right.data)<0){
               //进行右左旋转
               p=doubleRotateWithRight(p);
           }else {
               p=singleRotateRight(p);
           }
       }
   }
   else
    ;//if exist do nothing
   //重新计算各个结点的高度
   p.height = Math.max( height( p.left ), height( p.right ) ) + 1;

   return p;//返回根结点
}

##平衡二叉树删除操作的实现
  关于平衡二叉树的删除,我们这里给出一种递归的实现方案,和二叉查找树中删除方法的实现类似,但是在移除结点后需要进行平衡检测,以便判断是否需要进行平衡修复,主要明白的是,这种实现方式在删除时效率并不高,不过我们并不打算过多讨论它,更复杂的删除操作过程将放在红黑树中进行讨论。下面给出实现代码:

/**
 * 删除方法
 * @param data
 */
@Override
public void remove(T data) {
    if (data==null){
        throw new RuntimeException("data can\'t not be null ");
    }
    this.root=remove(data,root);
}

/**
 * 删除操作
 * @param data
 * @param p
 * @return
 */
private AVLNode<T> remove(T data,AVLNode<T> p){

    if(p ==null)
        return null;

    int result=data.compareTo(p.data);

    //从左子树查找需要删除的元素
    if(result<0){
        p.left=remove(data,p.left);

        //检测是否平衡
        if(height(p.right)-height(p.left)==2){
            AVLNode<T> currentNode=p.right;
            //判断需要那种旋转
            if(height(currentNode.left)>height(currentNode.right)){
                //LL
                p=singleRotateLeft(p);
            }else{
                //LR
                p=doubleRotateWithLeft(p);
            }
        }

    }
    //从右子树查找需要删除的元素
    else if(result>0){
        p.right=remove(data,p.right);
        //检测是否平衡
        if(height(p.left)-height(p.right)==2){
            AVLNode<T> currentNode=p.left;
            //判断需要那种旋转
            if(height(currentNode.right)>height(currentNode.left)){
                //RR
                p=singleRotateRight(p);
            }else{
                //RL
                p=doubleRotateWithRight(p);
            }
        }
    }
    //已找到需要删除的元素,并且要删除的结点拥有两个子节点
    else if(p.right!=null&&p.left!=null){

        //寻找替换结点
        p.data=findMin(p.right).data;

        //移除用于替换的结点
        p.right = remove( p.data, p.right );
    }
    else {
        //只有一个孩子结点或者只是叶子结点的情况
        p=(p.left!=null)? p.left:p.right;
    }

    //更新高度值
    if(p!=null)
        p.height = Math.max( height( p.left ), height( p.right ) ) + 1;
    return p;
}

#平衡二叉树的最少结点数和最多结点数问题
  关于最少结点数和最多结点数的问题,为了方便理解和简单化问题,这里我们假设AVL树的高度是h,N(h)表示高度为h的AVL树的结点数。对于求解高度为h的AVL树的最少结点数,则应该尽可能用最少结点数来填充该树,现在假设左子树填充到的高度为h-1,根据AVL树的特性,右子树的高度只能填充到h-2,因此高度为h的AVL树的最小结点数为:

N(h) = N(h-1) + N(h-2) + 1

其中:

  • N(h-1) 代表高度为h-1的左子树的最小结点数
  • N(h-2) 代表高度为h-2的右子树的最小结点数
  • 1 代表当前结点(根)。

求解上述递归公式可得(计算过程涉及线性代数的知识点,这里就不详细分析求解过程,毕竟我们主要求时间复杂度相关问题)其中n是AVL树的节点数:

N ( h ) = O ( 1.61 8 h ) = > h = 1.44 l o g n ≈ O ( l o g n ) N(h)=O(1.618^{h}) => h = 1.44logn ≈ O(logn) N(h)=O(1.618h)=>h=1.44lognO(logn)

  通过上述的递推公式,我们也可以发现最少结点数的求解公式恰好符合斐波那契数列的规律(F(n)=F(n-1)+F(n-2)),因此求解最少结点数也就变得容易多了。接着,我们采用同样的方法计算最大结点数,为了得到最大结点数,则左右子树的高必须相等,即都填充到h-1,由于结点都充满了,那么该树不仅是AVL树而且还是一个完全二叉树了,则会有如下公式:

N(h) = N(h-1) + N(h-1) + 1 = 2N(h-1) + 1

最终求解该递归公式得:

N ( h ) = O ( 2 h ) = > h = l o g n ≈ O ( l o g n ) N(h)=O(2^{h}) => h = logn ≈ O(logn) N(h)=O(2h)=>h=lognO(logn)

  因此在两种情况下,AVL树的性质可以确保带有n个结点的AVL树的高度为O(logn)。这也意味着AVL树的操作在时间复杂度上近乎于O(logn),也就不可能出现BST(二叉查找树)的最糟糕情况O(n)。
  ok~,关于AVL树就先聊到这,其他方法的实现跟上一篇的BST实现类似,大家直接看源码就行,本篇源码下载:github源码下载(含文章列表)

  • 50
    点赞
  • 171
    收藏
    觉得还不错? 一键收藏
  • 22
    评论
数据结构与算法 排序算法 内排序 八大基础排序 选择排序 简单选择排序 思想 每次选择最大的数插入到末尾 做法 外层for循环控制次数 内层for循环找出最大的值的角标 找出最大角标后,进行交换 优化思路 同时获取最大值和最小值,然后分别插入数组的首部和尾部 堆排序 思想 使用大顶堆的思想来排序,每次建堆后交换 做法 总体:建堆-替换 建堆 只要左子树或右子树大于当前根节点,则替换 替换后会导致下面的子树发生了变化,因此同样需要进行比较,直至各个节点实现父>子这么一个条件(递归) 交换排序 冒泡排序 思想 每次俩俩交换,将最大值交换到末尾 做法 外层for控制循环次数 内层for控制比较次数 每次循环之后,比较次数都会减少一次 优化思路 如果一趟排序后没有发生位置变化,那么此时就是有序了 快速排序 思想 每次将比支点小的放在支点左边,比支点大的放在支点右边 做法 外循环while只要i和j不碰撞查找 内层两个while循环分别查找出比支点小的和比支点大的角标 如果i<=j满足条件,就交换 一趟排序后,支点的左边都比支点小,支点右边都比支点大 只要满足L<j,i0的条件查找出要插入的何时位置 退出内层while循环后就找到了合适的位置插入 优化思路 二分查找插入,找合适位置的时候使用二分查找算法 希尔排序 思想 用增量来将数组进行分隔,直到增量为1。底层干的还是插入排序干的活 做法 最外层for外循环控制增量的数量,每次/2 第二层for循环控制每次增量那组开始进行插入排序,直至完毕 第三层while循环找到要插入到哪个位置 归并排序 思想 将两个已排好序的数组合并成一个有序的数组 做法 递归拆分出两个有序的数组,从mid的位置开始拆分,递归出口:只有一个值的时候就不用拆分了 合并两个有序的数据 分别往两个数组填充已有序的数据 比较两个数组的值谁小,谁小就放到我们的数组 如果比较完之后还有剩余的数据,那么用while直接添加到我们的总数组 优化思路 当递归到规模足够小时,利用插入排序 归并前判断一下是否还有必要归并 只在排序前开辟一次空间 基数(桶)排序 思想 分配,回收(分配到不同的位置上,然后回收)..不断分配..回收来进行排序,直到有序 做法 分配一个[array.length][10列]的二维数组来装我们的元素 最外层for循环控制要分配和回收的次数(根据最大值) 将元素的个、十、百位依次放到桶子上(第一次就是放个位,第二次放十位) 依据每列回收桶子,两个for循环 外排序 查找算法 二分查找 分块查找 哈希查找 贪心算法 求最小生成树的Prim算法和Kruskal算法 爬山问题 回溯算法 n皇后问题 动态规划Dynamic Planning 应用 求最长公共子序列LCS 矩阵连乘问题 爬楼梯问题 找零问题 0-1背包问题 分治算法Divide and Conquer 应用:归并排序 其它 Rabin fingerprints 文件指纹算法 BitMap 位图算法 BloomFilter 布隆过
平衡二叉树是一种特殊的二叉树,它的左右子树的高度差不超过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; } ``` 以上代码实现平衡二叉树的插入和序遍历操作。在插入操作,根据插入节点的值和当前节点的值的大小关系,不断递归向左或向右子树进行插入操作,并在递归返回时更新节点高度和进行平衡操作。在平衡操作,根据节点的平衡因子进行旋转操作,使树重新平衡。在序遍历操作,按照左子树、根节点、右子树的顺序遍历树的节点,输出节点的值。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值