zoukankan      html  css  js  c++  java
  • 数据结构和算法学习笔记十四:平衡二叉树(AVL树)

    一.简介:

      平衡二叉树(Self-Balcncing Binary Search Tree 或 Height-Balanced Binary Search Tree)是一种特殊的二叉排序树,其中每一个结点的左子树和右子树的高度差至多等于1.

      二叉树适用于在存储时需要保持有序的结构.平衡二叉树是一种优化的二叉树,平衡的作用是降低树的深度,这样能有效降低查找时的次数.

      在二叉树中,可以将结点左子树深度和右子树深度的差值成为平衡因子(Balance Factor),二叉树所有结点的平衡因子只能是0或±1.

    二.平衡二叉树的实现原理:

      1.左旋和右旋操作

      二叉树要想实现平衡,在每一次插入结点或移除结点时都需要判断这一操作是否导致了二叉树不平衡,如果不平衡需要移动结点的位置重新平衡二叉树.具体来说,我们通过计算结点的平衡因子来确定是否需要进行平衡,平衡的过程有两种,一种是当平衡因子大于1时需要左旋转,一种是平衡因子小于-1时需要进行右旋转.我们以下图的树为例:

      在树中,我们从下往上依次计算平衡因子(结点的左子树深度减去右子树深度的值),分别是:15结点014结点-111结点012结点-18结点010结点-2,我们会发现根结点10的深度值得绝对值大于1,这就说明10结点得左右不平衡,且右重左轻,那么我们自然可以想到我们可以加深10结点得左子树,降低10结点右子树的深度,实现这个操作方式就是左旋(将右侧向左侧旋转).左旋的过程首先不考虑11结点,这样就可以将10结点的右子树上拉,左子树及10结点本身下压,然后形成了如下如所示的结果:

      最后我们将拿去的11结点及其子树重新挂载到二叉树中,显然应该挂载为10结点的右子树.这样左旋操作就完成了.如下图所示:

      总结一下:左旋操作包括以下几步:1)使当前结点的右子结点的左子结点指向当前结点(将右子结点12上拉,当前结点10下压);2)让当前结点的右子结点指向上一步操作前当前结点右子结点的左子结点(将结点11重新挂回二叉排序树);3)如果进行左旋的这部分结点有父节点,还需要更改父节点的指向.

      结点的右旋操作就不再重复说明,和左旋操作完全相反.

      2.左旋和右旋的时机

      上面左旋和右旋的过程中已经有说明,当某个结点的平衡因子的绝对值大于1时需要在这个结点上进行左旋或右旋,根据平衡因子的定义,当平衡因子大于1时说明左子树深度大于右子树深度(左重右轻),需要进行右旋,当平衡因子小于-1时说明右子树深度大于左子树深度(左轻右重),需要进行左旋.但是下面一种情况却需要特别指出,如下图所示:

      我们计算各个结点的平衡因子,14结点013结点-118结点016结点110结点012结点-2.显然12结点的平衡因子为-2,左轻右重,应当左旋,那么我们左旋试一下:

      我们重新计算平衡因子会发现16结点的平衡因子又变成了2,仍然没有解决问题.那这是怎么一回事呢?

      对比两次旋转前的平衡因子的值我们发现,第一次旋转10结点和12结点的平衡因子都为负数,代表它们都是左轻右重的,而第二次旋转时12结点和16结点的平衡因子一正一负,代表它们一个左轻右重一个左重右轻.我们细细品味一下左旋的过程,会发现在左旋的过程中进行左旋的两个结点的平衡因子都会变大,以第一次旋转为例,10结点的左子树虽然没有变化,但是右子树从12变为了11,显然旋转前10结点的右子树深度值至少是11结点深度值加1,旋转后10结点右子树深度值就是11结点深度值,所以右子树深度值一定变小;12结点的左子树原来是11结点,旋转后11结点是10结点的右子树,由于旋转后10结点的深度值至少是11结点深度值加1,所以12结点的左子树的深度值一定变大,因此综合来看10结点和12结点的平衡因子都应该变大了.因此也就不难解释为什么这次的左旋没有让树重新平衡了,因为这次左旋前作左旋操作的12结点和16结点的平衡因子一正一负,所以当左旋后它们的平衡因子都变大后,原来平衡因子为正的16结点的平衡因子就会大于1.所以我们在进行左旋前一定要保证进行左旋的两个结点的平衡因子都不为正.那么对于第二种情况我们应该怎么办呢?很简单,我们先对平衡因子为正的16结点和13结点一起进行一次右旋操作让13结点变成12结点的右子结点,13结点的平衡因子减小了一定不为正,这时就可以把12结点和13结点一起进行左旋操作了,最终的结果如下图所示:

    三.平衡二叉树的实现(C#)

    /************************************
    * 创建人:movin
    * 创建时间:2021/7/26 21:06:11
    * 版权所有:个人
    ***********************************/
    using System;
    using System.Collections.Generic;
    using System.Text;
    
    namespace SearchCore
    {
        /// <summary>
        /// AVL树结点
        /// </summary>
        public class AVLNode
        {
            /// <summary>
            /// 结点中存储的数据,数据可根据需要修改
            /// </summary>
            public int Content { get; set; }
            /// <summary>
            /// 平衡因子
            /// </summary>
            public int BalanceFactor { get; private set; }
            /// <summary>
            /// 左子树深度值
            /// </summary>
            public int LeftChildDegree { get; private set; }
            /// <summary>
            /// 右子树深度值
            /// </summary>
            public int RightChildDegree { get; private set; }
            /// <summary>
            /// 左子树根结点
            /// </summary>
            public AVLNode LeftChild { get; set; }
            /// <summary>
            /// 右子树根结点
            /// </summary>
            public AVLNode RightChild { get; set; }
            public AVLNode(int content)
            {
                Content = content;
            }
            /// <summary>
            /// 右旋方法
            /// </summary>
            private void RightRotate(AVLNode parent,ref AVLNode root)
            {
                //将左子树暂存起来,不用校验左子树是否为空,因为触发右旋时平衡因子一定大于0,左子树不可能为空
                var temp = LeftChild;
                //将当前结点的左子树的右子树置为当前结点的左子树
                LeftChild = temp.RightChild;
                //当前结点变为当前结点原左子树的右子树
                temp.RightChild = this;
                //改变父节点的指针指向
                if(parent != null)
                {
                    if(parent.LeftChild == this)
                    {
                        parent.LeftChild = temp;
                    }
                    else if(parent.RightChild == this)
                    {
                        parent.RightChild = temp;
                    }
                }
                else
                {
                    root = temp;
                }
            }
            /// <summary>
            /// 左旋方法
            /// </summary>
            /// <param name="parent"></param>
            private void LeftRotate(AVLNode parent,ref AVLNode root)
            {
                //将右子结点暂存起来
                var temp = RightChild;
                //将当前结点的右子树的左子树置为当前结点的右子树
                RightChild = temp.LeftChild;
                //当前结点置为当前结点原右子树的左子树
                temp.LeftChild = this;
                //改变父节点的指针指向
                if (parent != null)
                {
                    if (parent.LeftChild == this)
                    {
                        parent.LeftChild = temp;
                    }
                    else if (parent.RightChild == this)
                    {
                        parent.RightChild = temp;
                    }
                }
                else
                {
                    root = temp;
                }
            }
            /// <summary>
            /// 添加子结点
            /// </summary>
            /// <param name="content"></param>
            public AVLNode AddChildNode(int content,AVLNode parent,ref AVLNode root)
            {
                AVLNode result = null;
                if (content < Content)
                {
                    if (LeftChild == null)
                    {
                        LeftChild = new AVLNode(content);
                        result = LeftChild;
                    }
                    else
                    {
                        result = LeftChild.AddChildNode(content,this,ref root);
                    }
                }
                else if (content > Content)
                {
                    if (RightChild == null)
                    {
                        RightChild = new AVLNode(content);
                        result = RightChild;
                    }
                    else
                    {
                        result = RightChild.AddChildNode(content,this,ref root);
                    }
                }
                //如果添加到了当前对象的子对象中,则需要重置左右子树的深度值并计算检验平衡因子绝对值是否大于1
                if (result != null)
                {
                    CalcAndCheckBalanceFactor(parent,ref root);
                }
                return result;
            }
            /// <summary>
            /// 计算并检验平衡因子
            /// </summary>
            public void CalcAndCheckBalanceFactor(AVLNode parent,ref AVLNode root)
            {
                //左子树深度值取左子树的左右子树深度值中的最大值加一
                LeftChildDegree = LeftChild == null ? 0 : (Math.Max(LeftChild.LeftChildDegree, LeftChild.RightChildDegree) + 1);
                RightChildDegree = RightChild == null ? 0 : (Math.Max(RightChild.LeftChildDegree, RightChild.RightChildDegree) + 1);
                //计算平衡因子
                BalanceFactor = LeftChildDegree - RightChildDegree;
                //计算完平衡因子后马上检验平衡因子的绝对值
                if (BalanceFactor > 1)
                {
                    if (LeftChild.BalanceFactor < 0)
                    {
                        LeftChild.ReLeftBalance(this,ref root);
                    }
                    ReRightBalance(parent,ref root);
                }
                else if (BalanceFactor < -1)
                {
                    if (RightChild.BalanceFactor > 0)
                    {
                        RightChild.ReRightBalance(this,ref root);
                    }
                    ReLeftBalance(parent,ref root);
                }
            }
            /// <summary>
            /// 重新左平衡(作左旋)
            /// </summary>
            public void ReLeftBalance(AVLNode parent,ref AVLNode root)
            {
                //临时记录当前结点的左子结点
                var temp = LeftChild;
                LeftRotate(parent,ref root);
                //左旋会引起旋转前当前结点和当前结点的左子结点的平衡因子变化,需要重新计算平衡因子
                //注意计算顺序
                CalcAndCheckBalanceFactor(temp,ref root);
                if(temp != null)
                {
                    temp.CalcAndCheckBalanceFactor(parent,ref root);
                }
            }
            /// <summary>
            /// 重新右平衡(作右旋)
            /// </summary>
            public void ReRightBalance(AVLNode parent,ref AVLNode root)
            {
                //临时记录当前结点的右子结点
                var temp = RightChild;
                RightRotate(parent,ref root);
                //左旋会引起旋转前当前结点和当前结点的左子结点的平衡因子变化,需要重新计算平衡因子
                //注意计算顺序
                CalcAndCheckBalanceFactor(temp,ref root);
                if(temp != null)
                {
                    temp.CalcAndCheckBalanceFactor(parent,ref root);
                }
            }
        }
    }
    /************************************
    * 创建人:movin
    * 创建时间:2021/7/26 21:11:28
    * 版权所有:个人
    ***********************************/
    using System;
    using System.Collections.Generic;
    using System.Text;
    
    namespace SearchCore
    {
        /// <summary>
        /// AVL树结构
        /// </summary>
        public class AVLTree
        {
            private AVLNode root;
            /// <summary>
            /// 根结点
            /// </summary>
            public AVLNode Root 
            {
                get
                {
                    return root;
                }
            }
            /// <summary>
            /// 插入新的结点
            /// </summary>
            /// <param name="content"></param>
            public AVLNode InsertAVLNode(int content)
            {
                if(root == null)
                {
                    root = new AVLNode(content);
                    return root;
                }
                return Root.AddChildNode(content,null,ref root);
            }
        }
    }
  • 相关阅读:
    项目经理所需要具备的素质
    项目经理的个人修养
    项目拖期怎么办
    创业起步的十大准备步骤
    注册(创办)公司的手续过程
    如何让女人每天更快乐
    全面比较:中美两国百姓的生活成本
    ADO.NET级别的事物
    respondsToSelector的使用
    用js在两个页面之间传值
  • 原文地址:https://www.cnblogs.com/movin2333/p/15053857.html
Copyright © 2011-2022 走看看