用Java实现平衡二叉树

​平衡二叉树的定义

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

  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);
}

以上便是平衡二叉树的插入思想和代码实现,欢迎大家交流!

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值