AVL树重要算法的实现

AVL树的定义

  • Adelson-Velsky-Landis Tree,是最先发明的自平衡二叉查找树,由作者名字命名
  • 一棵AVL树或者是空树,或者是具有下列性质二叉排序树
    1.它的左子树和右子树都是AVL树,
    2.且左子树和右子树的高度之差的绝对值不超过1
    3.(记住要统一,都是右减左,或都是左减右)。

AVL 树结构设计

class AVLTree {
    class AVLNode {
        AVLNode leftchild;
        AVLNode parent;
        AVLNode rightchild;
        int balance; // -1 ,0 , 1
        int key;

        public AVLNode() {
        }

        public AVLNode(int x) {
            key = x;
            balance=0;
            leftchild = parent = leftchild = null;
        }
    }

    private AVLNode root;
    private AVLNode cur;

    public AVLTree() {
        this.root = null;
        this.cur = null;
    }
}

在这里插入图片描述

单旋转

左单旋(RotateLeft )

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

//左单旋   ptr是发现的第一个不平衡的子树的根   先主干,再完善   一个函数只完成一个单一的功能,所以不管平衡因子的事
    private void RotateLeft(AVLNode ptr) {
        //第1步(newroot代替ptr,指向原来ptr的父亲)
        AVLNode newroot = ptr.rightchild;
        newroot.parent = ptr.parent;
        //第2步
        ptr.rightchild = newroot.leftchild;//父指向子
        if (newroot.leftchild != null) //子若有,就指向父
        {
            newroot.leftchild.parent = ptr;
        }
        //第3步
        newroot.leftchild = ptr;

        //ptr原来的父 指向 新子
        AVLNode pa = ptr.parent;
        if (pa == null)//说明ptr是根节点
        {
            root = newroot;
        } else {
            //原来ptr挂在双亲的左边,现在旋转后的新根就仍然挂在左边
            if (pa.leftchild == ptr) {
                pa.leftchild = newroot;
            } else {
                pa.rightchild = newroot;
            }
        }
        //上面处理完了ptr原来的父,现在ptr就可以指向自己的新父
        ptr.parent = newroot;
    }

右单旋 (RotateRight )

在这里插入图片描述

在这里插入图片描述

 /*
     右单旋  和左旋为镜像
     代码分为三块:
          旋转(1 2 3步)
          维护双亲
          维护新根
     */
    private void RotateRight(AVLNode ptr) {
        AVLNode newroot = ptr.leftchild;//旋转1
        newroot.parent = ptr.parent;
        ptr.leftchild = newroot.rightchild;//旋转2   newroot.rightchild可能为null
        if (newroot.rightchild != null) {
            newroot.rightchild.parent = ptr;
        }
        newroot.rightchild = ptr;//旋转3
        AVLNode pa = ptr.parent;//ptr 和 newroot从头到尾指向各自的同一个结点,没变过!!!!
        if (pa == null) {
            root = newroot;
        } else {
            if (pa.leftchild == ptr) {
                pa.leftchild = newroot;
            } else {
                pa.rightchild = newroot;
            }
        }
        ptr.parent = newroot;//旋转3
    }

双选转

先左后右双旋转 (RotationLeftRight) // LeftBalance

  • 要保证中序遍历有序,以及各节点平衡因子尽可能小
  • 如下图,是所有可能进行双旋的情况,h为0是特殊情况。
  • 本来右比左低一个,在左子树(D、F、E处)又插一个,导致根(A)的左子树已经失去平衡了,所以调整函数又叫LeftBalance
  • 看图发现,插入新结点前 和 插入新节点并调整后,层高并没有发生改变

在这里插入图片描述

  //    先左后右双旋转(保证中序有序  以及  各节点平衡因子尽可能小)   好像不会出现平衡因子大于1或小于-1的,因为插入的时候发现不平衡立马就进行旋转调整了
    private void LeftBalance(AVLNode ptr) {
        // wps图,ptr指向根(即A)!!!    leftsub指向B!!!
        AVLNode leftsub = ptr.leftchild, rightsub = null;
        switch (leftsub.balance) {
            case 0:
                System.out.println("left balance \n");
                break;
            case -1: //即B的右子树-B左子树的深度=-1   即插到D下面(看图知,调整后平衡因子为0)
                ptr.balance = 0;
                leftsub.balance = 0;//这里用不到rightsub,即E的平衡因子没有变化
                //不管是在D的左孩子还是右孩子插入,总之深度+1了,旋转方式是一样的
                RotateRight(ptr);
                break;
            case 1://B的右子树比左大1,即插入在B的右树       rightsub指向E!!!
                rightsub = leftsub.rightchild;
                //先专门调整  双旋后的banlance值
                switch (rightsub.balance) {
                    case 1://1G处插入
                        ptr.balance = 0;
                        leftsub.balance = -1;
                        break;
                    case 0:
                        ptr.balance = 0;
                        leftsub.balance = 0;
                        break;
                    case -1://-1F处插入
                        ptr.balance = 1;
                        leftsub.balance = 0;
                        break;
                }
                rightsub.balance = 0;
                //即F 或 G处插入,旋转方式是一样的
                RotateLeft(leftsub);//即以B结点为根的树
                RotateRight(ptr);// A
                break;
        }
    }

先右后左双旋转 (RotationRightLeft) // RightBalance

右子树失去平衡,需要先右旋再左旋,故为函数名称由来(和 先左旋后右旋相似)
如图:在F、G、E处插入都需要双旋
在这里插入图片描述
以在G处插入调整为例:
在这里插入图片描述
中序遍历顺序:BAFDGCE

  //   先右后左双旋转(wps图)
    private void RightBalance(AVLNode ptr) {   //ptr即A   rightsub即C !!!
        AVLNode rightsub = ptr.rightchild, leftsub = null;
        switch (rightsub.balance) {
            case 0:
                System.out.println("right balance \n");
                break;
            case 1://E处插放入
                ptr.balance = 0;
                rightsub.balance = 0;
                RotateLeft(ptr);
                break;
            case -1: //D的子树里插入
                //leftsub即D   !!!
                leftsub = rightsub.leftchild;
                switch (leftsub.balance) {
                    case 1://G处插入
                        ptr.balance = -1;//可以看图想象  也可以对比LeftBalance
                        rightsub.balance = 0;
                        break;
                    case 0:
                        ptr.balance = 0;
                        rightsub.balance = 0;
                        break;
                    case -1://F处插入
                        ptr.balance = 0;
                        rightsub.balance = 1;
                        break;
                }
                leftsub.balance = 0;
                RotateRight(rightsub);
                RotateLeft(ptr);
                break;
        }

    }

调整

可以参照前面单旋、双旋的图片理解

  private void Adjust_AVL(AVLNode ptr) {
        AVLNode pa = ptr.parent;
        boolean high = true;//标志高度是否发生改变

        //若ptr的双亲为空(即为根)  或 插入后层高没变  或 将由底往上第一个不平衡节点的子树调整好了,就不用 再 进行调整了
        while (high && pa != null) {
            //说明ptr插入到了其夫(pa)的左边
            if (pa.leftchild == ptr) {
                switch (pa.balance) {
                    case 0: //即本来平衡因子为0,插入到左边,右-左=-1,pa高度发生了改变!!!(需要下轮while调整其父的平衡因子)
                        pa.balance = -1;
                        break;
                    case 1://本来右-=1  现在插入左,右-=0,以pa为根的树的最大高度没有发生改变
                        pa.balance = 0;
                        high = false;
                        break;
                    case -1:
                        LeftBalance(pa);//左边不平衡了,所以调用该算法(里面有调整平衡因子),旋转后的层高 和 插入前的层高没有改变(故无需调整其父的平衡因子)
                        high = false;
                        break;
                }
            }
            else {
                switch (pa.balance) {
                    case 0:
                        pa.balance = 1;
                        break;
                    case -1:
                        pa.balance = 0;
                        high = false;
                        break;
                    case 1:
                        RightBalance(pa);
                        high = false;
                        break;
                }
            }
            //调整完 逐层往上找,该变 其父节点的平衡因子(针对 case 0:)
            ptr = pa;
            pa = ptr.parent;
        }
    }// 11:23

插入

插入方法同BST树的INSERT,只是多了一步插入后对树的调整。

 //    插入方法同BST树的INSERT;
    public boolean Insert(int kx) {

        boolean res = true;
        //空树时
        if (root == null) {
            root = new AVLNode(kx);
            return res;
        }

        cur = root;
        AVLNode pa = null;
        //在合适的分支找到底为止
        while (cur != null && cur.key != kx) {
            pa = cur;//找cur的孩子,所以升级当爸了
            cur = kx < cur.key ? cur.leftchild : cur.rightchild;
        }
        //有该值,就不插入(false,因为不允许重复)
        if (cur != null && cur.key == kx) {
            res = false;
        } else {
            cur = new AVLNode(kx);
            cur.parent = pa;//子指向父
            //没有该值,就将新结点挂在上面查找的最后一个位置的左或右孩子
            if (cur.key < pa.key) {
                pa.leftchild = cur;//父指向子
            } else {
                pa.rightchild = cur;
            }
            //链接好双亲后从插入的数据点处开始调整
            Adjust_AVL(cur);
        }
        return res;
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值