平衡二叉树(AVL树)构造、添加/删除元素(Java)

一、AVL树的定义

在计算机科学中,AVL树是最先发明的自平衡二叉查找树。在AVL树中任何节点的两个子树的高度最大差别为1,所以它也被称为高度平衡树。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。AVL树得名于它的发明者G. M. Adelson-Velsky和E. M. Landis,他们在1962年的论文《An algorithm for the organization of information》中发表了它。
——百度百科

二、AVL树添加、删除元素

说明:

AVLTress: 外部类
root: AVL树的根节点
nodeStack: 存放某次删除元素时遍历过的节点栈
Node: 内部类,节点类型
this:当前节点
left:当前节点的左子节点
right:当前节点的右子节点
value: 当前节点存放的值
height(): 当前节点高度
rightHeight:右子树高度
leftHeight():左子树高度
leftRoate():令当前节点左旋转
rightRotate():令当前节点右旋转
balance(): 调整以当前节点为根节点的子树平衡

1.添加

往AVL树 this 添加新节点 node 的思想是使用递归 :
外部类函数 add(E value) , 由根节点调用 root.add( new Node( value ) )
内部类函数 add(Node node),由当前节点调用

步骤:

  1. node.value <= this.value ,将其添加到当前树的左子树,
    this.left !=null 则 this.left.add( node )
    this.left ==null , this.left = node
  2. nodevalue > this.value , 将其添加到当前树的右子树
    this.right != null 则 this.right.add( node )
    this.right == null , this.right = node

在每次寻找适当的插入位置时,当前节点 this 遍历过的节点为所有可能发生失衡的节点,因此需要在插入节点完成后对当前节点进行平衡调整:this.balance()。

添加元素 代码:

	    /**
	     * Desc:    添加新元素 value
	     *
	     * @author pikachu
	     */
	    public void add(E value) {
   
	        if (root == null) {
   
	            root = new Node(value);
	
	        } else {
   
	            root.add(new Node(value));
	        }
	    }
        /**
         * Desc:    将 node加入到当前节点 this
         *
         * @param node 待加入节点
         * @author pikachu
         */
        private void add(Node node) {
   
            if (node.value.compareTo(this.value) <= 0) {
   
                if (this.left != null) {
   
                    this.left.add(node);
                } else {
   
                    this.left = node;
                }
            } else {
   
                if (this.right != null) {
   
                    this.right.add(node);
                } else {
   
                    this.right = node;
                }
            }
            balance();
        }

2.旋转

往AVL树中添加元素时可能会破环树的平衡,处理形况分为4种:LL、RR、LR、RL。
对树进行旋转时总是以高度较大的子树为圆心旋转,从而降低其高度,
左旋转:逆时针旋转
右旋转:顺时针旋转

LL

LL型:破环平衡的节点在左子树的左子节点,leftHeight>rightHeight && left.leftHeight >= left.rightHeight,4(this)绕2(left)右转(顺时针)

RR

RR型:破环平衡的节点在右子树的右子节点,rightHeight>leftHeight && right.rightHeight >= right.leftHeight,6(this)绕8(right)左转(逆时针)

LR

LR型:破环平衡的节点在左子树的右子节点,leftHeight > rightHeight &&
left.rightHeight > left.leftHeight。

  1. 2(left)绕3(left.right)左转(逆时针)
  2. 5(this)绕3(left)右旋转(顺时针)

RL

RL型:破环平衡的节点在右子树的左子节点, rightHeight > leftHeight &&
right.leftHeight > right.rightHeight。

  1. 5(right)绕4(right.left)右转(顺时针)
  2. 2(this)绕4(right)左旋转(逆时针)

调节平衡代码

        /**
         * Desc:调节当前节点的子树平衡
         */
        private void balance() {
   
            // 平衡调整
            if (rightHeight() - leftHeight() > 1) {
   
                if (right.rightHeight() >= right.leftHeight()) {
   
                    //RR型
                    leftRotate();
                } else {
   
                    //RL型
                    right.rightRotate();
                    leftRotate();
                }
                return;
            }

            if (leftHeight() - rightHeight() > 1) {
   
                if (left.leftHeight() >= left.rightHeight()) {
   
                    //LL型
                    rightRotate();
                } else {
   
                    //LR型
                    left.leftRotate();
                    rightRotate();
                }
            }
        }

3.删除

remove( Node parent, Node delNode ) : 移除preNode的子树中的node节点(如果存在), 当待删除的节点有左右子树时,将右子树的最小节点作为 parent的新子节点, 删除后将遍历路径上的节点nodesStack平衡。

说明:
this 当前节点
parent 当前节点的父节点
delNode 待删除节点
rightMinNode 扫描 this 右子树最小节点(this.right != null 时实际删除的节点)
preNode rightMinNode的父节点,初始为this

寻找待删除节点时的路径可能不只是每次遍历的当前节点 this ,还有 preNode 遍历过的节点。如以下情况:

此时 preNNode 遍历过的节点 17 失衡,因此每次当 preNode 发生移动时也需要将 preNode 压入 nodeStack。

步骤:

  1. delNode < this,往左子树查找删除,若左子树为空则结束
  2. delNode > this,往右子树查找删除,若右子树为空则结束
  3. delNode = this 找到待删除的节点 this
    3.1 this.right不为空,在 this.right的子树中寻找最小节点 rightMinNode,作为新子树的根节点
    3.1.1preNode和rightMinNode发生移动,待删除节点的右子节点有左子树, 并在该树(根节点 rightMinNode,left = null, right 不一定为 null)中找到最小值。
    3.1.2 preNode和rightMinNode没有移动,待删除节点的右子节点无左子树,this.right.left = null,
    右子树的最小节点就是this.right,令this拼接上this.right.right。
    3.1.3 将 this.value 替换为 rightMinNode.value
    3.2 this.right为空
    3.2.1 this =parent.right),parent.right = this.left;
    this = parent.left, parent.left = this.left;
    3.2.2 this = parent ,删除树中根节点,替换根节点 root = root.left;

删除元素时也可能破环树的平衡,需要记录删除时遍历的节点 nodeStack,然后将其逆序取每个遍历过的节点 node 进行平衡化 : node.balance();

删除元素代码

	    /**
	     * Desc:
	     *
	     * @param value 待移除的元素
	     * @author pikachu
	     */
	    public void remove(E value) {
   
	        if (root == null) {
   
	            System.out.println("Tree is empty");
	        } else {
   
	            root.remove(root, new Node(value));
	            // 本次删除结束,将nodes清空
	            nodeStack.clear();
	        }
	    }
    
        /**
         * Desc:移除preNode的子树中的node节点(如果存在),
         * 当待删除的节点有左右子树时,将右子树的最小节点作为 parent的新子节点,
         * 删除后将遍历路径上的节点nodesStack平衡
         *
         * @param parent  当前节点的父节点
         * @param delNode 待删除节点
         * @author pikachu
         */

        private void remove(Node parent, Node delNode) {
   
            nodeStack.push(this);
            //  1.delNode < this,往左子树查找删除;若左子树为空则结束
            if (delNode.value.compareTo(this.value) < 0) {
   
                if (this.left != null) {
   
                    this.left.remove(this, delNode);
                }
            }
            //  2.delNode > this,往右子树查找删除;若右子树为空则结束
            else if (delNode.value.compareTo(this.value) > 0) {
   
                if (this.right != null) {
   
                    this.right.remove(this, delNode);
                }
            }
            //  3.delNode = this 找到待删除的节点 this
            else if (delNode.value.compareTo(this.value) == 0) {
   
                Node preNode = this;
                //  3.1 this.right不为空,在 this.right的子树中寻找最小节点 rightMinNode,作为新子树的根节点
                if (this.right != null) {
   
                    
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值