vlambda博客
学习文章列表

平衡二叉树的插入与实现

平衡二叉树的定义

在谈平衡二叉树之前,首先了解一下二叉排序树。空树或者具有以下特性的二叉树就是二叉排序树:

  1. 若左子树非空,则左子树上所有结点关键字的值均小于根节点的关键字的值。

  2. 若右子树非空,则右子树上所有结点关键字的值均大于根节点的关键字的值。

  3. 左右子树本身也分别是一棵二叉排序树。
根据上述定义可知二叉排序树的中序遍历是一个递增的有序序列。但是二叉排序树的平均查找长度与二叉树的形态有关,如果二叉排序树是一个只有左(右)孩子的单支树,则其平均查找长度与单链表无异,因此我们为了避免书的高度增长过快,降低二叉排序树的性能,定义了平衡二叉树。
若二叉排序树的左右子树的高度只差的绝对值不超过1,则这样的二叉排序树称为平衡二叉树(AVL)。左右子树的高度差称作平衡因子,所以平衡二叉树的平衡因子的值只能是-1、0或1。

平衡二叉树的插入

为了让二叉排序树在每一次插入和删除新结点时都使树保持平衡,所以每次操作之后都要对树的结构进行调整,其基本思想是:
当在二叉排序树中插入或删除一个结点时,首先检查其插入路径上的结点是否因为此次操作导致了不平衡,若导致了不平衡,则先找到插入路径上离插入节点最近的平衡因子的绝对值大于1的结点A,再对以A为跟的子树,在保持二叉排序树特性的前提下,调整结点位置使之重新平衡。


其插入过程前半部分是二叉排序树的插入过程,但在新结点插入后,若造成某结点不平衡,则需要做出调整,一般可将失去平衡后进行调整的规律归纳为下列四种情况。
  • LL平衡旋转(右单旋转):在结点A的左孩子的左子树上插入新结点,A的平衡因子由1增至2,导致以A为根的子树失去平衡,需要一次向右的旋转。将A的左孩子B向右上旋转代替A成为根节点,将A结点向右下旋转称为B的右子树的根节点,而B的原右子树作为A结点的左子树。如下图所示,结点旁的数字表示平衡因子,方块是对应子树,H表示子树高度。
  • RR平衡旋转(左单旋转):在结点A的右孩子的右子树上插入新结点,A的平衡因子由-1减至-2,导致以A为根的子树失去平衡,需要一次向右的旋转。将A的右孩子B向左上旋转代替A称为根节点,将A结点向左下旋转称为B的左子树的根结点,而B的原左子树则作为A结点的右子树。
平衡二叉树的插入与实现
  • LR平衡旋转(先左后右双旋转):在结点A的左孩子的右子树上掺入新结点,A的平衡因子由1增至2,导致以A为根的子树失去平衡,需要进行两次旋转操作,先向左旋转后向右旋转。先将A结点的左孩子B的右子树的根节点C向左上旋转提升到B结点的位置,然后再把C结点向右上旋转提升到A结点。
  • RL平衡旋转(先右后左双旋转):在结点A的右孩子的左子树上插入新结点,A的平衡因子由-1减至-2,导致以A为跟的子树失去平衡,需要进行两次旋转操作,先右旋转后左旋转。先将A结点的右孩子B的左子树的根节点C向右上旋转提升至B结点的位置,然后再把该C结点向左上旋转提升到A结点的位置。
平衡二叉树的实现
弄清了如何使平衡二叉树保持平衡的原理,那么便可以用代码实现,从上述四种情况来看,实现的难点在于旋转,只要把旋转弄清楚,基本上整个数就能完成。
首先我们需要定义一个树的结点。
 
   
   
 
public class Node {
public Node parent ; // 指向父节点
public Node left ; // 指向左子树
public Node right ; // 指向右子树
public int data ; // 节点数据
public int depth ; // 节点深度
public int balance ; // 是否平衡
public Node( int data){
this. data=data ;
depth= 1 ;
balance= 0 ;
left= null;
right= null;
parent= null;
}
}
从平衡二叉树的特性来看,我们需要知道平衡二叉树的平衡因子。
 
   
   
 
public int calcBalance(Node p) {
int left_depth ;
int
right_depth ;
// 左子树的深度
if(p. left != null){
left_depth = p. left. depth ;
} else{
left_depth = 0 ;
}
// 右子树的深度
if(p. right != null){
right_depth = p. right. depth ;
} else{
right_depth = 0 ;
}

return left_depth-right_depth ;
}
平衡因子又是根据左右子树的高度来的,因此,我们需要计算高度。
 
   
   
 
public int calcDepth(Node p) {
int depth = 0 ;
if
(p. left != null){
depth = p. left. depth ;
}
if(p. right != null && depth < p. right. depth){
depth = p. right. depth ;
}

depth++ ;
return
depth ;
}
右旋操作涉及到祖父结点、父结点和右子结点。
 
   
   
 
public void right_rotate(Node p) {
Node pParent = p. parent ;
Node pLeftSon = p. left ;
Node pRightGrandSon = pLeftSon. right ;
pLeftSon. parent = pParent ;
if
(pParent != null){
if(p == pParent. left){
pParent. left = pLeftSon ;
} else if(p == pParent. right){
pParent. right = pLeftSon ;
}
}

pLeftSon. right = p ;
p. parent = pLeftSon ;

p. left = pRightGrandSon ;
if
(pRightGrandSon != null){
pRightGrandSon. parent = p ;
}

p. depth = calcDepth(p) ;
p. balance = calcBalance(p) ;
pLeftSon. depth = calcDepth(pLeftSon) ;
pLeftSon. balance = calcBalance(pLeftSon) ;
}
左旋操作涉及到祖父结点、父结点和左子结点。
 
   
   
 
public void left_rotate(Node p) {
Node pParent = p. parent ;
Node pRightSon = p. right ;
Node pLeftGrandSon = pRightSon. left ;

pRightSon. parent = pParent ;
if
(pParent != null){
if(p == pParent. right){
pParent. right = pRightSon ;
} else if(p == pParent. left){
pParent. left = pRightSon ;
}
}

pRightSon. left = p ;
p. parent = pRightSon ;
p. right = pLeftGrandSon ;
if
(pLeftGrandSon != null){
pLeftGrandSon. parent = p ;
}

p. depth = calcDepth(p) ;
p. balance = calcBalance(p) ;
pRightSon. depth = calcDepth(pRightSon) ;
pRightSon. balance = calcBalance(pRightSon) ;
}
然后便是数据的插入。
 
   
   
 
public void insert(Node root , int data){
// 数据小于根结点,左递归插入
if(data < root. data){
if(root. left != null){
insert(root. left , data) ;
} else{
root. left = new Node(data) ;
root. left. parent=root ;
}
} else{ // 数据大于根结点,右递归插入
if(root. right != null){
insert(root. right , data) ;
} else{
root. right = new Node(data) ;
root. right. parent = root ;
}
}
// 插入之后计算平衡因子
root. balance = calcBalance(root) ;
// 左子树高则右旋
if(root. balance >= 2){
// 右孙高先左旋
if(root. left. balance == - 1){
left_rotate(root. left) ;
}
right_rotate(root) ;
}
// 右子树高则左旋
if(root. balance <= - 2){
// 左孙高则先右旋
if(root. right. balance == 1){
right_rotate(root. right) ;
}
left_rotate(root) ;
}
// 调整之后重新计算平衡因子和树的高度
root. balance = calcBalance(root) ;
root. depth = calcDepth(root) ;
}
以上便是平衡二叉树的插入思想和代码实现,欢迎大家交流!