【算法&数据结构体系篇class35】有序表 (上篇)AVL树

文章介绍了搜索二叉树的基本概念,包括插入、删除和查询操作。重点讨论了AVL树作为平衡二叉搜索树的特性,强调了它的严格平衡性,以及如何通过左旋和右旋操作来维护树的平衡。此外,还提到了AVL树的平衡调整策略,如LL、RR、LR和RL四种情况的处理。
摘要由CSDN通过智能技术生成

一、搜索二叉树

搜索二叉树一定要说明以什么标准来排序

经典的搜索二叉树,树上没有重复的用来排序的key

如果有重复节点的需求,可以在一个节点内部增加数据项

二、搜索二叉树查询key(查询某个key存在还是不存在)

 1)如果当前节点的value==key,返回true

2)如果当前节点的value<key,当前节点向右移动

3)如果当前节点的value>key,当前节点向左移动

4)如果当前节点变成null,返回false

 

三、搜索二叉树插入新的key

 和查询过程一样,但当前节点滑到空的时候,就插入在这里

四、搜索二叉树删除key

0)先找到key所在的节点

1)如果该节点没有左孩子、没有右孩子,直接删除即可

2)如果该节点有左孩子、没有右孩子,直接用左孩子顶替该节点

3)如果该节点没有左孩子、有右孩子,直接用右孩子顶替该节点

4)如果该节点有左孩子、有右孩子,用该节点后继节点顶替该节点

后继节点 也就是节点的右子树的最左节点 就是该节点的下个节点 称后继节点 因为搜索二叉树中序遍历就是有序的升序排列 下个节点自然就是找右子树的最左节点

 搜索二叉树特别不讲究

1)基础的搜索二叉树,添加、删除时候不照顾平衡性

2)数据状况很差时,性能就很差

给搜索二叉树引入两个动作:左旋、右旋

五、AVL树、SB树、红黑树的共性

1)都是搜索二叉树

2)插入、删除、查询(一切查询)搜索二叉树怎么做,这些结构都这么做

3使用调整的基本动作都只有左旋、右旋

4插入、删除时,从最底层被影响到的节点开始,对往上路径的节点做平衡性检查

5因为只对一条向上路径的每个节点做O(1)的检查和调整,所以可以做到O(logN)

六、AVL树、SB树、红黑树的不同

1)平衡性的约束不同

AVL树最严格、SB树稍宽松、红黑树最宽松

2)插入、删除和搜索二叉树一样,但是额外,做各自的平衡性调整。各自的平衡性调整所使用的动作都是左旋或者右旋

七、AVL

平衡二叉搜索树, 左右树高度差小于2,即为平衡  搜索树表示有序,才能搜索,左中右 中序遍历是升序

1)最严格的平衡性,任何节点左树高度和右树高度差不超过1

2往上沿途检查每个节点时,都去检查四种违规情况:LL、RR、LR、RL

3)不同情况虽然看起来复杂,但是核心点是:

LL(做一次右旋)、RR(做一次左旋)

LR和RL(利用旋转让底层那个上到顶部)

LL 就是说cur的左节点的左节点 路线是较高的一个子树导致的高度差大于1   cur进行右旋就可以修复平衡性

LR 就是cur的左节点的右节点 路线是较高的子树     将其左节点的右节点弹到cur 也就是对左节点进行左旋,左节点的右节点位置就来到左节点,然后再对cur进行右旋,就会使得前面来到左节点的左右子节点来到cur 完成平衡

RR 对cur 进行左旋

RL 对cur.r 进行右旋,使得cur.r.l来到 cur.r位置 然后对cur进行左旋,使得cur.r的节点来到cur 完成平衡性

如果同时存在LL与LR 也就是当前cur.l的左右子树高度相同 都可以影响平衡性 都比cur.r的高度差大于1 那么就对cur进行 右旋即可
如果同时存在RR与RL 就对cur进行 左旋即可

代码演示:

package class35;

import java.util.Comparator;

/**
 * AVL树  平衡二叉搜索树, 左右树高度差小于2,即为平衡  搜索树表示有序,才能搜索,左中右 中序遍历是升序
 * 1)最严格的平衡性,任何节点左树高度和右树高度差不超过1
 * <p>
 * 2)往上沿途检查每个节点时,都去检查四种违规情况:LL、RR、LR、RL
 * <p>
 * 3)不同情况虽然看起来复杂,但是核心点是:
 * LL(做一次右旋)、RR(做一次左旋)
 * LR和RL(利用旋转让底层那个上到顶部)
 */
public class AVLTreeMap1 {
    //定义AVL节点类, K类型表示树的 key 支持比较 继承比较类 用于后续的大小比较找对应的节点位置
    public static class AVLNode<K extends Comparable<K>, V> {
        public K k;           //AVL树节点的 key值
        public V v;           //AVL树节点的 value值
        public AVLNode<K, V> l; //当前节点的左子节点
        public AVLNode<K, V> r; //当前节点的右子节点
        public int h;         //AVL树该节点作为根节点的子树的高度  作为后续平衡性调整的关键因子

        public AVLNode(K key, V value) {    //构造器 初始化节点属性 键值
            k = key;
            v = value;
            h = 1;               //高度为当前节点本身 1
        }
    }

    //定义AVL树类 AVL树类实现的有序表结构    有序表的K支持比较大小
    public static class AVLTreeMap<K extends Comparable<K>, V> {
        public AVLNode<K, V> root;      //AVL树的根节点,只需一个根节点,就能得到整个树
        public int size;               //AVL树中有多少个Key,表示有多少个节点

        public AVLTreeMap() {
            root = null;   //初始化 根节点为空
            size = 0;      //初始化 树节点树0
        }

        //右旋操作:当前cur右旋,则cur会来到右子节点位置,
        //并且如果cur.l.r非空,就需要将这个子树指向 cur.l 也就是来到右子节点的左子节点  因为是基于平衡且搜索树,要保证顺序正确,
        //cur.l来到cur位置 那么其cur.l.r 是比cur.l大的 因为是升序 右节点是较大的 所以就把这个cur.l.r 给到 cur的左节点 他是比cur小的 所以放左
        //cur.l会来到cur的位置  最后返回刷新后的cur节点
        public AVLNode<K, V> rightRotate(AVLNode<K, V> cur) {
            AVLNode<K, V> left = cur.l;     //右旋则需要将左子节点提到当前cur位置
            cur.l = left.r;                //将左节点left的右树 赋值给cur节点的左树
            left.r = cur;                  //左节点left的右树 赋值给cur节点  这样就完成右旋 左子节点来到顶部原cur位置,cur来到其右子节点

            //完成右旋,只是完成了第一步,因为右旋后会更换节点位置 也就是cur 与 cur.left两个节点 需要刷新
            //他们的高度, 也就是返回各自节点的左右子树的较大值然后+1本身节点 就是新高度
            //注意需要先刷新cur 因为是底部的节点, 再去刷新left的节点
            cur.h = Math.max((cur.l != null ? cur.l.h : 0), (cur.r != null ? cur.r.h : 0)) + 1;
            left.h = Math.max((left.l != null ? left.l.h : 0), (left.r != null ? left.r.h : 0)) + 1;

            //最后需要返回当前新的cur节点 也就是cur的左节点
            return left;
        }

        //左旋操作:同理 换个方向
        public AVLNode<K, V> leftRotate(AVLNode<K, V> cur) {
            AVLNode<K, V> right = cur.r;     //左旋则需要将右子节点提到当前cur位置
            cur.r = right.l;                //将右节点right的左树 赋值给cur节点的右树
            right.l = cur;                  //右节点right的左树 赋值给cur节点  这样就完成左旋  右子节点来到顶部原cur的位置 而cur就来到其左子节点位置

            //完成左旋,只是完成了第一步,因为左旋后会更换节点位置 也就是cur 与 cur.right两个节点 需要刷新
            //他们的高度, 也就是返回各自节点的左右子树的较大值然后+1本身节点 就是新高度
            //注意需要先刷新cur 因为是底部的节点, 再去刷新left的节点
            cur.h = Math.max((cur.l != null ? cur.l.h : 0), (cur.r != null ? cur.r.h : 0)) + 1;
            right.h = Math.max((right.l != null ? right.l.h : 0), (right.r != null ? right.r.h : 0)) + 1;

            //最后需要返回当前新的cur节点 也就是cur的左节点
            return right;
        }

        //AVL树节点调整平衡  当添加节点或者删除节点 有可能涉及节点位置调换那么就有可能存在平衡被打破
        //也就是当左右树高度差大于1时 就需要就分情况去进行左右旋调整平衡
        //存在破坏平衡的情况可能是 LL LR RR RL 四种情况
        //比如LL 就是说cur的左节点的左节点 路线是较高的一个子树导致的高度差大于1   cur进行右旋就可以修复平衡性
        // LR    就是cur的左节点的右节点 路线是较高的子树     将其左节点的右节点弹到cur 也就是对左节点进行左旋,左节点的右节点位置就来到左节点,然后再对cur进行右旋,就会使得前面来到左节点的左右子节点来到cur 完成平衡
        // RR    对cur 进行左旋
        // RL    对cur.r 进行右旋,使得cur.r.l来到 cur.r位置 然后对cur进行左旋,使得cur.r的节点来到cur 完成平衡性
        //如果同时存在LL与LR 也就是当前cur.l的左右子树高度相同 都可以影响平衡性 都比cur.r的高度差大于1 那么就对cur进行 右旋即可
        //如果同时存在RR与RL 就对cur进行 左旋即可
        //最后返回调整平衡性后的当前节点
        public AVLNode<K, V> maintain(AVLNode<K, V> cur) {
            if (cur == null) return null;                        //如果当前节点是空节点 直接返回空
            int leftHeight = cur.l != null ? cur.l.h : 0;       //定义出当前cur节点的左右子树的高度
            int rightHeight = cur.r != null ? cur.r.h : 0;

            if (Math.abs(leftHeight - rightHeight) > 1) {         //当前左右子树如果高度差大于1 就表示不平衡,需要调整位置使得平衡
                //分析情况看看左右子树那边高,
                if (leftHeight > rightHeight) {
                    //L左子树高 那么就再获取左子树L的 左右子树高度
                    int leftLeftHeight = cur.l != null && cur.l.l != null ? cur.l.l.h : 0;
                    int leftRightHeight = cur.l != null && cur.l.r != null ? cur.l.r.h : 0;
                    if (leftLeftHeight >= leftRightHeight) {
                        //如果cur.l.l 大于等于 cur.l.r  那么我们就对当前cur节点进行依次 右旋 就可以使得整个树平衡
                        cur = rightRotate(cur);
                    } else {
                        //否则 cur.l.l 小于 cur.l.r  就需要对cur.l进行左旋,使得cur.l.r上移来到cur.l的位置 而cur.l就会来到cur.l.l
                        //这里注意需要用cur.l重新接收左旋后的新节点 函数返回的节点
                        cur.l = leftRotate(cur.l);
                        //接着再将cur节点 右旋,使得cur.l上移来到cur的位置
                        //然后注意需要用cur重新接收右旋后的新节点
                        //最终就是把cur.l.r上移到了cur位置  完成平衡调整
                        cur = rightRotate(cur);
                    }
                } else {
                    //如果cur.r高于cur.l  那么就需要对cur的右子树进行同理的操作
                    int rightLeftHeight = cur.r != null && cur.r.l != null ? cur.r.l.h : 0;
                    int rightRightHeight = cur.r != null && cur.r.r != null ? cur.r.r.h : 0;
                    if (rightRightHeight >= rightLeftHeight) {
                        cur = leftRotate(cur);    // 右节点cur.r下: 右子树高度大于等于左子树  对cur节点进行一次左旋
                    } else {
                        cur.r = rightRotate(cur.r);  //右节点cur.r下: 右子树高度小于左子树 先对cur.r进行右旋 使得cur.r.l上移来到cur.r 同时要刷新cur.r 接收函数返回的当前节点
                        cur = leftRotate(cur);       //当前节点cur: 进行左移 使得 cur.r的节点继续上移来到cur  同时要刷新cur节点  完成最终的平衡调整
                    }
                }
            }
            return cur;        //最后返回调整平衡后的当前节点
        }

        //如果插入key  那么就要找要插入的位置 如果AVL树存在 key值的节点 那么就直接返回这个节点 后续就直接修改v值
        //如果不存在 那么就根据搜索顺序 往左右下移 直到空 返回的就是key的父节点
        public AVLNode<K,V> findLastIndex(K key){
            //定义两个辅助变量指向root 进行遍历
            AVLNode<K,V> pre = root;
            AVLNode<K,V> cur = root;
            //开始遍历cur 节点 ,
            while(cur != null){
                //让pre指向cur
                pre = cur;
                //如果key值等于当前cur的key 那么就是找到最后经过的节点 不用再继续下去了  直接退出
                if(key.compareTo(cur.k) == 0){
                    break;
                } else if(key.compareTo(cur.k) < 0){
                    //如果key是比当前cur小的 那么就去cur的左子树找,因为左子树值小 区间就在左子树
                    cur = cur.l;
                }else{
                    //如果key比当前cur大,那么就需要继续到cur的右子树找,找到最靠近key的节点
                    cur =cur.r;
                }
            }
            return pre;  //最后返回辅助变量指向的就是最终的结果
        }

        //获取大于等于指定的key的最靠近的节点 比如key=8  AVL树节点有1,3,6,7,13,10 那么就返回10 最接近8 且大于等于8  如果存在8 就返回8
        public AVLNode<K,V> findLastNoSmallIndex(K key){
            AVLNode<K,V> ans = null;
            AVLNode<K,V> cur = root;
            while(cur != null){
                if(key.compareTo(cur.k) == 0){
                    //key 等于当前cur的key  ans指向当前节点cur 退出
                    ans = cur;
                    break;
                }else if(key.compareTo(cur.k) < 0){
                    //如果key 小于cur的key 说明cur是在目标范围内的,ans赋值当前节点 接着下层到做子树看看有没有更小更接近key的节点
                    ans = cur;
                    cur = cur.l;
                }else{
                    //如果key大于cur 说明cur 不在范围内,需要往右子树 扩大到 大于等于key值
                    cur = cur.r;
                }
            }
            return ans;  //最后返回ans 节点
        }

        //获取小于等于指定的key的最靠近的节点 比如key=8  AVL树节点有1,3,6,7,13,10 那么就返回7 最接近8 且小于等于8  如果存在8 就返回8
        public AVLNode<K,V> findLastNoBigIndex(K key){
            AVLNode<K,V> ans = null;
            AVLNode<K,V> cur = root;
            while(cur != null){
                if(key.compareTo(cur.k) == 0){
                    ans = root;
                    break;
                } else if(key.compareTo(cur.k) > 0){  //key 大于 cur.k 说明是在范围内 赋值ans 并且接着往右子树扩大看有没有更接近的节点
                    ans = cur;
                    cur = cur.r;
                } else{
                    cur = cur.l;
                }
            }
            return ans;
        }

        //添加一个K,V值,传入的root节点从整个AVL树的根节点开始往下遍历
        //因为添加的过程可能会需要调整平衡性,并且会不断往最低个下层 找到位置添加 然后再返回新调整好的节点返回上层 就需要递归
        //为了方便 我们递归函数给一个 返回当前节点的返回值
        public AVLNode<K,V> add(AVLNode<K,V> cur, K key, V value){
            if(cur == null){
                //如果是一个空的树,那么添加的节点 就直接返回一个新节点就可以了
                return new AVLNode<>(key,value);
            } else{
                //如果非空,那么就判断大小 如果key 比cur的大 那么就需要cur.r 去递归右子树 否则就是递归左子树 并且要返回接收刷新节点左右子树
                //这里没有等于的情况,因为会在 调用添加节点的 put方法去提前做相等的判断
                if(key.compareTo(cur.k) > 0){
                    cur.r = add(cur.r,key,value);
                }else{
                    cur.l = add(cur.l,key,value);
                }
                //然后需要刷新当前cur节点的高度
                cur.h = Math.max(cur.l != null ? cur.l.h : 0 , cur.r != null ? cur.r.h : 0) + 1;
                //然后再调整cur的平衡性
                return maintain(cur);
            }
        }

        //在cur的AVL树上,删掉key的对应节点 , 返回树的新头节点,因为可能会涉及平衡调整 ,头节点需要返回
        public AVLNode<K,V> delete(AVLNode<K,V> cur, K key){
            if(key.compareTo(cur.k) > 0){
                //key 大于 cur 那么就递归 cur右子树 并且返回新的右子树头节点
                cur.r = delete(cur.r, key);
            } else if(key.compareTo(cur.k) < 0){
                //key 小于 cur 递归cur左子树
                cur.l = delete(cur.l, key);
            } else{ //相等值,那么就表示要删的节点找到了
                if(cur.l == null && cur.r == null){
                    cur = null;    //如果没有左右子节点 直接删除 cur赋值空
                } else if(cur.l != null && cur.r == null){  //左非空 右空 直接将左子节点上移
                    cur = cur.l;
                } else if(cur.r != null && cur.l == null){  //右非空 左空 右子节点上移
                    cur = cur.r;
                } else {
                    //左右子节点非空,那么删除cur节点 就需要找后继节点 替换cur 也就是cur的右子树的最左子节点,这个节点就是接着比cur大的最接近的下个节点
                    //思路就是 先在cur.r子树中 找到最左子节点  然后把该节点  调用delete 把这个cur.r子树的 最左子节点删除
                    //然后再把节点指向重新指向 把最左子节点提上来,他的左右子树 就是cur的左右子树
                    AVLNode<K,V> next = cur.r;
                    while(next.l != null){
                        next = next.l; //下沉到cur右子树的最左节点 直到为空停 next就是最左子节点
                    }
                    cur.r = delete(cur.r,next.k);   //调用delete方法 把cur.r的子树的 最左节点next删除 并返回新头节点给到cur.r
                    next.l = cur.l;                 //删除子树的最左节点 next已经保存了最左节点 提到被删除cur节点位置 将cur的左右子树 赋值给next节点
                    next.r = cur.r;
                    cur = next;                     //最后cur节点再指向新的节点
                }
            }

            //来到这里 就已经把cur的值删除 并且挂好了对应的新节点 接着判断 cur的高度 并且 要向上刷新是否需要调整平衡性 调用函数返回
            if(cur != null){//注意可能删除key之后 就是空树 所以要判断非空 再刷新高度
                cur.h = Math.max(cur.l != null ? cur.l.h : 0, cur.r != null ? cur.r.h : 0) + 1;
            }
            return maintain(cur);
        }

        //返回整个树的节点个数
        public int size(){
            return size;
        }

        //返回是否树中存在key的对应节点
        public boolean containsKey(K key){
            if(key == null) return false;   //空值 直接返回false

            AVLNode<K,V> node = findLastIndex(key);  //通过调用对应函数 找到key对应需要插入的位置 如果存在key 返回就是对应节点 否则就是返回key的父节点 插入左或者右节点
            return node != null && key.compareTo(node.k) == 0;    //如果节点非空 并且值节点 返回true  否则就是false  空树的情况下node就会为空
        }


        //给外部调用的 添加节点方法,添加到整个树
        public void put(K key,V value){
            if(key == null) return;       //如果key为空 返回退出返回
            AVLNode<K,V> node = findLastIndex(key); //调用函数 找到key值对应的节点位置
            if(node !=null && key.compareTo(node.k) == 0){
                node.v = value;   //如果节点非空 key值就等于node 那么就是直接覆盖当前node的value值
            }else {
                //如果不相等 那么就是新节点 size先加1  然后调用add函数 在root整个树种加入 key,value 最后在把新头节点 返回给root 因为可能会涉及平衡性调整位置
                size++;
                root = add(root, key, value);
            }
        }

        //给外部调用 移除节点的方法
        public void remove(K key){
            if(key == null) return;   //空值 直接退出返回
            if(containsKey(key)){   //删除前先判断 是否树种存在key  存在那么就size-- 然后调用delete函数 对整个树root进行删除key 最后返回新的头节点返回给root
                size--;
                root = delete(root, key);
            }
        }

        //给外部调用 获取节点key对应的value
        public V get(K key){
            if(key == null) return null; //如果空 就直接返回Null
            AVLNode<K,V> node = findLastIndex(key);   //调用函数找到对应key节点weiz
            if(node != null && key.compareTo(node.k)==0){
                //节点非空且是等于key 说明找到了节点 返回对应的v
                return node.v;
            }
            return null;   //如果没有找到直接返回Null
        }


        //外部调用 获取树的第一个值 树是有序的 按中序遍历 左中右是升序 所以第一个节点就是 整个树的最左节点就是最小的节点
        public K firstKey(){
            if(root == null) return null;
            AVLNode<K,V> node = root;   //获取root节点
            while (node.l != null){
                node = node.l;   //节点一直往左子节点下沉直到空
            }
            return node.k; //找到最左节点 返回K
        }

        //外部调用 获取树的最后一个值 树是有序的 按中序遍历 左中右是升序 所以最后一个节点就是 整个树的最右节点就是最大的节点
        public K lastKey(){
            if(root == null) return null;
            AVLNode<K,V> node = root;   //获取root节点
            while (node.r != null){
                node = node.r;   //节点一直往右子节点下沉直到空
            }
            return node.k; //找到最右节点 返回K
        }

        //外部调用 获取对应key 小于等于key的最大的一个节点 也就是最接近Key的节点
        //比如key=8  AVL树节点有1,3,6,7,13,10 那么就返回7 最接近8 且小于等于8  如果存在8 就返回8
        public K floorKey(K key){
            if(key == null) return null;
            AVLNode<K,V> node = findLastNoBigIndex(key); //调用函数 找到小于等于key 最接近key的树中的节点返回
            return node == null ? null : node.k;
        }

        //外部调用 获取对应key 大于等于key的最小的一个节点 也就是最接近Key的节点
        //比如key=8  AVL树节点有1,3,6,7,13,10 那么就返回10 最接近8 且大于等于8  如果存在8 就返回8
        public K ceilingKey(K key){
            if(key == null) return null;
            AVLNode<K,V> node = findLastNoSmallIndex(key); //调用函数 找到大于等于key 最接近key的树中的节点返回
            return node == null ? null : node.k;
        }
    }


}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值