【数据结构】AVL树

AVL树

叫作高度平衡二叉树,性质-》

    首先是二叉树,具有二叉树的左小右大,之所以叫做高度平衡,他可以根据插入的顺序来自动调节平衡, 使左右子树的高度差绝对值小于1。so 加入了一个平衡因子来判平衡,并旋转等一些操作来到达平衡。

       

下面为一个数学规律:

时间复杂度:log2^N

结点树为n,完全二叉树的高 度h = log2n

高度h从零开始计

设高度为h的平衡二叉树最少有多少结点:

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

类似与斐波那契数列

h = log2n

基本操作

  构造结点-》 由概念可以知道相比于二叉树多了平衡因子balance factor--bf

    平衡因子:右子树高度减去左子树的高度,平衡数值:-1 0 1

template <class K, class V>

class AVLtreeNode

{

        K _key;

        V _value;

        int _bf; //平衡因子

        AVLtreeNode<K, V>* _left;

        AVLtreeNode<K, V>* _right;

        AVLtreeNode<K, V>* _parent;

        AVLtreeNode<K, V>(const K& k, const V& v)

               :_key(k)

               , _value(v)

               , _bf(0)

               , _left(NULL)

               , _right(NULL)

               , _parent(NULL)

        {}

};

这里加了_parent指针,为了之后旋转的时候更方便。如果不加,后面要用栈。

插入操作-》 插入类似与BST,但是多了_bf, 

    所以在日常的插入后,要调节平衡因子,所以我写了IsBalance()函数,用来更新新的_bf

    该函数可以判断新增的节点是左还是右来调整_bf,但这里又有问题,当_bf为-2,2时就   破坏了平衡,需要通过旋转来调节平衡,如何旋转在后续讲。需要注意的是如果_bf从-1,1变为0,实际节点高度是不影响的,所以不需要向上调节了。所以代码如下;

bool inster(const K& k, const V& v)
        {
               //1.空树时直接开辟空间插入
               //2.根据key找到,有相等的key替换
               //3.没相等的,找到应该插入的位置
               //4.根据多种情况插入节点->>   类似BST 插入函数,注意多了par,bf;
               //5.从插入的地方判断平衡因子 ->> IsBanlace(Node);
               //6.根据平衡因子旋转。
               if (!_root)
               {
                       _root = new Node(k, v);
                       return true;
               }
               Node* root = _root;
               Node* par = _root;
               while (root)
               {
                       if (k < root->_key) {
                              root = root->_left;
                              par = root;
                       }
                       else if (k > root->_key)
                       {
                              root = root->_right;
                              par = root;
                       }
                       else {
                              root->_value = v;
                              return false;
                       }
               }
               if (!root)
               {
                       root = new Node(k, v);
                       if (k < par->_key)
                       {
                              par->_left = root;
                              root->_parent = par;
                              IsBalance(root);
                       }
                       else
                       {
                              par->_right = root;
                              root->_parent = par;
                              IsBalance(root);
                       }
               }
                 while (root->_parent)
               {
                       root = root->_parent;
               }
               _root = root;
               return true;
        }

调节平衡-》所以接下来我们来看看,破坏平衡的几种情况,也就是选择的分离,及如何解决。

旋转:

插入者,被破坏者

RR 左单旋:插入者在被破坏者的右子树的右子数上,也就是RR端,将被破坏者的R结点向上提,自己下去左边,RF作为自己的R。

就像这条红线向左折了一下,就叫左单旋,就这么记吧...

RR左单旋的情况-》 A的_bf本来是1,插入new后变为2,  1-2 左单旋,且A B的_bf同号。可以看出需要断开连接的地方有俩个:A-R  , AR - ARL代码如下

void RRroatr(Node* &root)
        {
               assert(root);
               Node* cur = root;
               Node* par = cur->_parent;
               //root要指向旋转后的根
               root = cur->_right;
               cur->_right = root->_left;
               if (root->_left)
               root->_left->_parent = cur;
               root->_left = cur;
               cur->_parent = root;
               cur->_bf = root->_bf = 0;
               root->_parent = par;
               //不影响上级
               if (par)
               {
                   if (root->_key < par->_key)
                             par->_left = root;
                         else if (root->_key > par->_key)                                       
                         par->_right = root;
               }
        }

LL 右单旋:同理得:插入者是被破坏者的LL节点,使用LL单旋,被破坏者为A,A降为B的右,BR作为自己L,向右折叫做右单旋

LL右单旋的情况-》 A _bf  -1->-2 右单旋, 且A 与B的_bf同号。断开的地方:A-AL ,AL-ALR 代码如下

void LLroatr(Node* &root)
        {
               assert(root);
               Node* cur = root;
               Node* par = cur->_parent;
               root = cur->_left;
               cur->_left = root->_right;
               if(root->_right)
               root->_right->_parent = cur;
               root->_right = cur;
               cur->_parent = root;
               root->_bf = cur->_bf = 0;
               root->_parent = par;
               if (par)
               {
                       if (root->_key < par->_key)
                              par->_left = root;
                       else if (root->_key > par->_key)
                              par->_right = root;
               }
        }

RL 右左双旋

先右旋,构造出左旋,再左旋,此时CL可以看作为空。

RL右左旋情况-》可以看出A也是从 1 -> 2,但是B为-1,所以A B异号,这种情况为RL旋转。

旋转过程:1.右旋,对A的右子树进行右旋

                 2.这时候情况和左旋时一样,直接对A左旋。

LR 左右双旋

先左旋,构造出右旋,在右旋

L左右旋情况-》可以看出A也是从 -1 -> -2,但是B为1,所以A B异号,这种情况为LR旋转。

旋转过程:1.左旋,对A的右子树进行右旋

                 2.这时候情况和右旋时一样,直接对A左旋。

【注意】因为在程序中加入一个节点是,保证加入前这个树为平衡树,所以不考虑 在非平衡树下加入节点的情况,即 RRR端插入,RLL端插入。

程序中需要考虑三角为子树的情况。都为满足以上四种。做题时出现特殊情况,另行判断即可。

AVL树的旋转是最重要的地方,是大佬想出来的方法,很难通俗的讲清楚,但只要自己动手画一下图,就能很好理解。

所以最后的调节平衡函数为

void IsBalance(Node* root)

        {

               //根据新增节点位置,添加减少par的BF

               //1,-1 -》0 高度不变,不向上递增break

               //1, -1 继续向上递增

               //2,-2 判断旋转种类,旋转结束退出

               //递增至根结束

               Node* par = root->_parent;

               while (par)

               {

                       if (root->_key < par->_key)

                       {

                              par->_bf += -1;

                       }

                       else if (root->_key > par->_key)

                       {

                              par->_bf += 1;

                       }

                       if (par->_bf == 0)

                              break;

                       if (par->_bf == 2)

                       {

                              if (root->_bf == 1)

                                      RRroatr(par);

                              else if (root->_bf == -1)

                                      RLroatr(par);

                       }

                       else (par->_bf == -2)

                       {

                              if (root->_bf == 1)

                                      LLroatr(par);

                              else if (root->_bf == -1)

                                      LRroatr(par);

                       }

                       par = par->_parent;

                       root = root->_parent;

               }

        }

插入代码初次写完,出现bug及原因

中途中断 1.选择代码没有用引用,影响了循环,双旋转

               2.旋转完后_bf没有归0,影响循环

               3.没有判断结点是否为空,就取_par.  中断

次序错误 1.插入结束后没有更新_root结点,再次插入就次序出错。

节点的平衡因子从1,-1变为0则深度不变。若深度改变则要判断对祖节点的影响,是否影响平衡。

判断是否是平衡树 : 时间复杂度 n^2

前序改为后序,降低时间复杂度 O(1)

bool _IsBalance(Node* root, int& height)
{
    if(root == NULL)
    {
        height = 0;
        return 1;
    }
    int leftH = 0;
    int right = 0;
    if(!(_IsB(root->_left, lH)))
        return 0;
    if(!(_IsB(root->_right, rH)))
        return 0;
    height = lh > rh ? lh+1:rh+1;
    return abs(rh-lh) < 2;
}

返回高度使用了引用,改变一个数的值。

检查平衡因子

猜你喜欢

转载自blog.csdn.net/qq_32672481/article/details/79824849
今日推荐