AVL树的插入操作(旋转)图解

时间:2023-12-26 17:50:13
===================================================================

AVL树的概念

      在说AVL树的概念之前,我们需要清楚二茬搜索树的概念。对于二叉搜索树,我们知道它可以降低查找速率,但是如果一个二叉搜索树退化成一棵只剩单支的搜索树,此时的查找速率就相当于顺序表中查找元素,效率变低,时间复杂度由原来的O(logN)变为O(N)。
        此时就有了AVL(高度平衡二叉搜索树),从它的名字就能知道它也是一棵二叉搜索树,只是它在插入元素的时候,每插入一个新节点的时候就会调整整棵树的结构,从而来保证这课搜索树的平衡,即每一个节点的左右子树高度差的绝对值不超过1.
那么AVL树的概念就可以总结如下:
  满足以下性质的二叉搜索树:
  1、左右子树都是AVL树
  2、左右子树的高度之差的绝对值不超过1
     AVL树的插入操作(旋转)图解
AVL树的插入操作(旋转)图解
那么对于像这样的AVL树,如果它有n个节点,则它的高度可以保持在log(n),那么它的平均搜索时间复杂度也就是O(log(n)了。
================================================================

AVL树的插入

1、平衡因子
      AVL树也是一棵二叉搜索树,所以它的插入是和二叉搜索树的插入操作类似的,只是需要加上调整高度的操作,那么就需要在节点的那个结构体类型中加一个用来反应这个节点的左右孩子高度的变量,平衡因子bf。
      定义bf的值等于节点右孩子的高度减去左孩子的高度。如果节点的左右孩子高度相等,则bf等于0;如果左孩子比右孩子高度大1,则bf取-1;如果右孩子高度比左孩子高度大1,则bf取1.那么如果不是上面的这三种情况,就不满足AVL树的定义了,即出现bf的绝对值大于1的时候就要进行高度调整了,所以就是当bf等与2或者-2的时候就要进行平衡化。
       那么当给一棵本来就平衡的AVL树中插入一个新节点P的时候,从节点P到根节点的路径上,每个节点为根的子树的高度都可能增加1,即平衡因子发生改变,所以执行一次插入操作后,都需要沿路径向根节点回溯,修改各节点的平衡因子,而如果遇到了哪一个节点的bf变成2或-2的时候就要进行平衡化处理,即调整棵树的高度。
2、节点类型的结构体
     我们已经知道在结构体中需要加一个变量 bf,而且我们在修改bf的时候是需要回溯的,所以如果我们还存放了每个节点的父节点就比较方便了,那么可以设计如下的节点结构体类型:
 template<class K>
struct AVLTreeNode
{
K _key;
int _bf;
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent; AVLTreeNode(const K& key, const V& value)
:_key(key),
_bf(),
_left(NULL),
_right(NULL),
_parent(NULL)
{}
};
3、平衡化处理
       在前面已经说了当更新到某个节点的平衡因子变成-2或者2的时候就需要进行平衡化处理,我们在AVL树中的平衡化处理采用的是旋转。对于平衡化处理中的旋转分为以下几种情况:
      》左单旋:
      》右单旋
      》左右双旋
      》右左双旋
下面是对于上述四种情况的图解:
     》【左单旋】当前节点较高右子树的右侧插入一个新节点,该子树高度增加1导致节点平衡因子从1变为变为2而不平衡
AVL树的插入操作(旋转)图解
            AVL树的插入操作(旋转)图解
     》【右单旋当前节点较高的左子树左侧插入一个新节点,该子树的高度增加1,导致该节点的平衡因子从-1变为-2而不平衡
 AVL树的插入操作(旋转)图解
            AVL树的插入操作(旋转)图解
     》【左右双旋当前节点的平衡因子为-1,在当前节点较高的左子树的右子树b或者c中插入一个节点,该子树的高度增加1,使当前节点的左孩子的平衡因子变为1,当前节点的平衡因子变成-2,导致AVL树不再平衡,需要进行调整,采用先左后右双旋
(PPNode是10的父节点)
         AVL树的插入操作(旋转)图解
AVL树的插入操作(旋转)图解AVL树的插入操作(旋转)图解
       可以看到在这里插入的节点是插在了6节点的左子树,那么它当然也可以插入到6的右子树中,而且还可以是上图中的方框代表的子树都是空这种情况,此时就相当于是插入6这个节点。这样的话,最后更新节点的平衡因子就要注意了,我们稍后再分析;
     》【右左双旋】当前节点的平衡因子为1,在当前节点较高的右子树的左子树b或者c插入新节点该子树的高度增加1,当前节点的右孩子的平衡因子变成-1,当前节点的平衡因子变成2,导致AVL树不再平衡,需要进行调整,采用先右后左双旋
      (PPNode是5的父节点)AVL树的插入操作(旋转)图解
           AVL树的插入操作(旋转)图解AVL树的插入操作(旋转)图解
》》》》注意:上面两个双旋的图解只是其中的一种情况,在上面左右双旋的下面我已经提出来了,这里需要注意非常重要的一点,就是你插入了新节点之后会改变哪几个节点的平衡因子,显然插入的地方不一样没得到的结果也会有差异;
       因为每插入一个节点都要向上更新bf,我们总结一下遇到什么情况应该旋转,采用什么旋转:
若parent->bf=-2或者2的时候需要进行调整
》parent->bf=2,说明它的右树比左树高,设它的右孩子为pcur
    >若pcur->bf==1,执行左单旋
    >若pcur->bf==-1,执行右左双旋旋
》parent->bf=-2,说明它的左树比右树高,设它的左孩子为pcur
    >若pcur->bf==-1,执行右单旋
    >若pcur->bf==1,执行左右双旋单旋
我们可以看到,在旋转过程中平衡因子发生改变的节点只有parent和pcur这两个节点,那么旋转之后该怎样修改这两个节点的平衡因子呢。
     >对于左单旋和右单旋的情况,parent和pcur的平衡因子都会变为0,所以在旋转完成后把它们的平衡因子置成0
     >对于双旋,我们最后更新平衡因子的时候是需要分情况的
        AVL树的插入操作(旋转)图解
AVL树的插入操作(旋转)图解
     那么通过上图的说明,我们就可以看出来,最终影响parent和subR节点平衡因子的是subRL这个节点,主要就是看新插入的节点到底是插在它的左子树还是右子树,当然还有一种情况就是图中矩形代表的子树都为空的情况,也就是subRL就是要插入的节点,那么总共就是这三种情况,对应出来如下:
          》subRL的平衡因子是0(插入的节点就是它本身)
          》subRL的平衡因子是-1(新节点插在subRL的左子树)
          》subRL的平衡因子是1 (新节点插在subRL的右子树)
 对应的最终subR和parent的平衡因子调整我就不再赘述了,画一画很容易明白的。    
================================================================

程序代码:

        >>插入 insert函数
     bool Insert(const K& key, const V& value)
{
if (_root == NULL)
{
_root = new Node(key, value);
return true;
}
Node* pcur = _root;
Node* parent = NULL;
while (pcur)
{
if (pcur->_key == key)
return false;
else if (pcur->_key < key)
{
parent = pcur;
pcur = pcur->_right;
}
else
{
parent = pcur;
pcur = pcur->_left;
}
}
if (parent->_key < key)
{
pcur = parent->_right = new Node(key, value);
pcur->_parent = parent;
}
else
{
pcur = parent->_left = new Node(key, value);
pcur->_parent = parent;
} while (parent)
{
//修正平衡因子
if (pcur == parent->_left)
{
parent->_bf--;
}
if (pcur == parent->_right)
{
parent->_bf++;
}
//
if (parent->_bf == )
break;
else if (parent->_bf == - || parent->_bf == )
{
pcur = parent;
parent = pcur->_parent;
}
else //parent->bf -2 || 2
{ if (parent->_bf == -)
{
if (pcur->_bf == -) //右单旋
RotateR(parent);
else //左右双旋
RotateLR(parent);
}
else
{
if (pcur->_bf == ) //左单旋
RotateL(parent);
else //右左双旋
RotateRL(parent);
}
break;
}
}
return true;
}

>>旋转

     void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
//换指向
parent->_left = subLR;
subL->_right = parent; if (subLR)
{
subLR->_parent = parent;
} Node* PParent = parent->_parent; //判断parent是否有父节点
if (PParent)
{
if (parent == PParent->_left)
{
PParent->_left = subL;
subL->_parent = PParent;
}
else
{
PParent->_right = subL;
subL->_parent = PParent;
}
}
else
{
_root = subL;
subL->_parent = NULL;
}
parent->_parent = subL;
//修改bf
subL->_bf = ;
parent->_bf = ;
} //
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
//调整指向
subR->_left=parent;
parent->_right = subRL; if (subRL) //如果subRL非空
{
subRL->_parent = parent;
} Node* PPNode = parent->_parent;
if (PPNode)
{
if (PPNode->_left == parent)
PPNode->_left = subR;
else
PPNode->_right = subR; //subR的父节点改变
subR->_parent = PPNode;
}
else
{
_root = subR; //根节点
subR->_parent = NULL;
}
parent->_parent = subR;
/*修改bf*/
parent->_bf = subR->_bf = ;
} //双旋(左右、、右左)
void RotateRL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf; RotateR(parent->_right);
RotateL(parent); //调整subR和parent的平衡因子
if (bf == -)
subR->_bf = ; // subR的bf在左旋中已经置0了,这里就没有再写
else if (bf == )
parent->_bf = -; else
{
parent->_bf = ;
subRL->_bf = ;
}
} void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotateL(parent->_left);
RotateR(parent); //调整parent和subL的平衡因子
if (bf == -)
parent->_bf = ; //subL的bf在左旋中已经置0了,这里就没有再写
else if (bf == )
subL->_bf = -; //parent的bf在左旋中已经置0了
else
{
subL->_bf = ;
parent = ;
}
}