AVL树的实现


1. 定义:带有平衡条件的二叉搜索树。平衡条件为其每个节点的左子树和右子树的高度最多差1。该平衡条件保证了树的深度为O(log N).
2. 具体实现过程:

a. AVL树节点的定义:

声明了节点值,左右孩子节点和节点高度

    /**
     * 定义AVL树的节点
     * @param <T>
     */
    private class AVLTreeNode<T extends Comparable<T>>{
        T key;//节点值
        int height;//节点高度
        AVLTreeNode<T> left;
        AVLTreeNode<T> right;
        public AVLTreeNode(T key, AVLTreeNode<T> left, AVLTreeNode<T> right){
            this.key = key;
            this.left = left;
            this.right = right;
            this.height = 0;
        }
    }
b. AVL树中节点的查找:

先比较节点值与传入的值,再递归查找左右子树

    /**
     * 根据传入的值和树,实现在树中查找是否存在某个节点值为key
     * @param x
     * @param key
     * @return
     */
    private AVLTreeNode<T> search(AVLTreeNode<T> x, T key){
        if (x == null){
            return null;
        }
        //进行比较
        int cmp = key.compareTo(x.key);
        //查找左子树
        if (cmp < 0){
            return search(x.left, key);
        } 
        //查找右子树
        else if (cmp > 0){
            return search(x.right, key);
        }
        //查找成功,返回
        else {
            return x;
        }
    }

    public AVLTreeNode<T> search(T key){
        return search(root, key);
    }

非递归版查找

 private AVLTreeNode<T> iterativeSearch(AVLTreeNode<T> x, T key){
        while (x != null){
            int cmp = key.compareTo(x.key);
            if (cmp < 0){
                x = x.left;
            } else if (cmp > 0){
                x = x.right;
            } else {
                return x;
            }
        }
        return x;
    }

    public AVLTreeNode<T> iterativeSearch(T key){
        return iterativeSearch(root, key);
    }
3. 查找AVL树中的最值:

最大值:不断向右子树查找,直到某个节点的右孩子节点为空,则返回当前节点

private AVLTreeNode<T> maximum(AVLTreeNode<T> tree){
        if (tree == null){
            return null;
        }
        while (tree.right != null){
            tree = tree.right;
        }
        return tree;
    }

    public T maximum(){
        AVLTreeNode<T> p = maximum(root);
        if (p != null){
            return p.key;
        }
        return null;
    }

最小值:不断向左子树查找,直到某个节点的左孩子节点为空,则返回当前结点

 public T minimum(){
        AVLTreeNode<T> p = minimum(root);
        if (p != null){
            return p.key;
        }
        return  null;
    }

    private AVLTreeNode<T> minimum(AVLTreeNode<T> tree){
        if (tree == null){
            return null;
        }
        while (tree.left != null){
            tree = tree.left;
        }
        return tree;
    }
4. 对AVL树进行插入节点:

当我们进行插入操作时,若不会破坏AVL树平衡的特性,则直接插入即可,否则在插入完成之前我们要进行一些简单的修正,我们称其为旋转。

出现不平衡的四种情况,我们假定出现不平衡的节点为a.

1. 对 a 的左儿子的左子树进行一次插入
2. 对 a 的左儿子的右子树进行一次插入
3. 对 a 的右儿子的右子树进行一次插入
4. 对 a 的右儿子的左子树进行一次插入
其中情况1和情况3,我们通过对树的一次单旋转即可完成,而对于情况2和情况4,我们需要通过双旋转完成。

单旋转:

LL的旋转,即情况1:

single_rotation
    /**
     * 传入不平衡的节点,进行LL旋转,返回平衡后的节点
     * @param k2
     * @return
     */
    private AVLTreeNode<T> leftLeftRotation(AVLTreeNode<T> k2){
        AVLTreeNode<T> k1;
        //进行旋转
        k1 = k2.left;
        k2.left = k1.right;
        k1.right = k2;
        //更新节点的高度
        k2.height = Math.max(height(k2.left), height(k2.right)) + 1;
        k1.height = Math.max(height(k1.left), k2.height) + 1;
        return k1;
    }

RR旋转,即情况3:

RR_rotation
    /**
     * 实现RR旋转
     * @param k1
     * @return
     */
    private AVLTreeNode<T> rightRightRotation(AVLTreeNode<T> k1){
        AVLTreeNode<T> k2;
        //进行旋转
        k2 = k1.right;
        k1.right = k2.left;
        k2.left = k1;
        //更新高度
        k1.height = Math.max(height(k1.left), height(k1.right)) + 1;
        k2.height = Math.max(height(k2.right),k1.height) + 1;
        return k2;
    }
双旋转:

LR旋转,即情况2

LR_Rotation
    /**
     * 实现LR旋转
     * @param k3
     * @return
     */
    private AVLTreeNode<T> leftRightRotation(AVLTreeNode<T> k3){
        //获取到k1节点
        AVLTreeNode<T> k1 = k3.left;
        //对k1进行RR旋转
        k3.left = rightRightRotation(k1);
        //再对k3进行LL旋转
        return leftLeftRotation(k3);
    }

RL旋转,即情况4:

RL_Rotation
    /**
     * 实现RL旋转
     * @param k1
     * @return
     */
    private AVLTreeNode<T> rightLeftRotation(AVLTreeNode<T> k1){
        //获取到k3节点
        AVLTreeNode<T> k3 = k1.right;
        //对k3节点进行LL旋转
        k1.right = leftLeftRotation(k3);
        //对k1节点进行RR旋转
        return rightRightRotation(k1);
    }

实现插入节点操作:

    /**
     * 实现插入节点的操作
     * @param tree
     * @param key
     * @return
     */
    private AVLTreeNode<T> insert(AVLTreeNode<T> tree, T key){
        //若当前结点为空,则创建一个新节点,并返回
        if (tree == null){
            return new AVLTreeNode<T>(key, null, null);
        }
        //进行比较
        int cmp = key.compareTo(tree.key);
        //进入左子树
        if (cmp < 0){
            //对左子树进行递归插入
            tree.left = insert(tree.left, key);
            //若插入后,存在不平衡情况
            if (height(tree.left) - height(tree.right) == 2){
                //若插入的位置为左边,则进行LL旋转
                if (key.compareTo(tree.left.key) < 0){
                    tree = leftLeftRotation(tree);
                } 
                //若插入的位置在右边,则进行LR旋转
                else {
                    tree = leftRightRotation(tree);
                }
            }
        } 
        //进入右子树
        else if (cmp > 0){
            //对右子树进行递归插入
            tree.right = insert(tree.right, key);
            //若插入后,存在不平衡的情况
            if (height(tree.right) - height(tree.left) == 2){
                //若插入的位置在右边,则进行RR旋转
                if (key.compareTo(tree.right.key) > 0){
                    tree = rightRightRotation(tree);
                } 
                //若插入的位置在左边,则进行RL旋转
                else {
                    tree = rightLeftRotation(tree);
                }
            }
        }
        //存在该节点了,不做操作
        else {
            System.out.println("添加失败:不允许添加相同的节点");
        }
        //更新节点的高度
        tree.height = Math.max(height(tree.left), height(tree.right)) + 1;
        return tree;
    }

    public void insert(T key){
        root = insert(root, key);
    }

实现删除节点操作:

    /**
     * 实现删除节点操作,并返回删除的节点
     * @param tree
     * @param delete
     * @return
     */
    private AVLTreeNode<T> remove(AVLTreeNode<T> tree, AVLTreeNode<T> delete){
        //没有删除的节点,直接返回空
        if (tree == null || delete == null){
            return null;
        }
        //进行比较
        int cmp = delete.key.compareTo(tree.key);
        //进入左子树
        if (cmp < 0){
            //对左边进行递归删除
            tree.left = remove(tree.left, delete);
            //存在不平衡的情况
            if (height(tree.right) - height(tree.left) == 2){
                //因为删除的是左子树节点,则要调整的为右子树节点
                AVLTreeNode<T> r =tree.right;
                //左大于右,则进行RL旋转
                if (height(r.left) > height(r.right)){
                    tree = rightLeftRotation(tree);
                } 
                //右高于左,则进行RR旋转
                else {
                    tree = rightRightRotation(tree);
                }
            }
        } 
        //进入右子树
        else if (cmp > 0){
            //进行递归删除
            tree.right = remove(tree.right, delete);
            //出现不平衡的情况
            if (height(tree.left) - height(tree.right) == 2){
                //删除的是右子树节点,因此要调整的是左子树
                AVLTreeNode<T> l = tree.left;
                //右高于左,则进行LR旋转
                if (height(tree.right) > height(tree.left)){
                    tree = leftRightRotation(tree);
                } 
                //左高于右,则进行LL旋转
                else {
                    tree = leftLeftRotation(tree);
                }
            }
        } 
        //进行删除当前结点
        else {
            //左右孩子都不为空
            if ((tree.left != null) && (tree.right != null)){
                //左高于右,那么要调整的是左子树
                if (height(tree.left) > height(tree.right)){
                    //获取到左子树的最大节点,因为此节点不含孩子
                    AVLTreeNode<T> max = maximum(tree.left);
                    //左子树最大节点替换当前结点
                    tree.key = max.key;
                    //删除左子树的最大节点
                    tree.left = remove(tree.left,max);
                } else {
                    //获取右子树的最小节点
                    AVLTreeNode<T> min = minimum(tree.right);
                    //用右子树的最小节点替换当前结点
                    tree.key = min.key;
                    //删除右子树的最小节点
                    tree.right = remove(tree.right, min);
                }
            }
            //若只存在左孩子或右孩子,则使孩子替换当前结点
            else {
                tree = (tree.left != null) ? tree.left : tree.right;
            }
        }
        return tree;
    }

    public void remove(T key){
        AVLTreeNode<T> z = search(root, key);
        if (z != null){
            root = remove(root, z);
        }
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,519评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,842评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,544评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,742评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,646评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,027评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,513评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,169评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,324评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,268评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,299评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,996评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,591评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,667评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,911评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,288评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,871评论 2 341

推荐阅读更多精彩内容

  • 基于树实现的数据结构,具有两个核心特征: 逻辑结构:数据元素之间具有层次关系; 数据运算:操作方法具有Log级的平...
    yhthu阅读 4,224评论 1 5
  • 树的概述 树是一种非常常用的数据结构,树与前面介绍的线性表,栈,队列等线性结构不同,树是一种非线性结构 1.树的定...
    Jack921阅读 4,422评论 1 31
  • 1 序 2016年6月25日夜,帝都,天下着大雨,拖着行李箱和同学在校门口照了最后一张合照,搬离寝室打车去了提前租...
    RichardJieChen阅读 5,068评论 0 12
  • 一直以来,我都很少使用也避免使用到树和图,总觉得它们神秘而又复杂,但是树在一些运算和查找中也不可避免的要使用到,那...
    24K男阅读 6,725评论 5 14
  • 记得那年他二十二岁,也正值民国二十二年。早已成年的他渐渐少了来自父母的约束,便开始计划出游一番。时间恰好定在他...
    沈清和_阅读 196评论 0 0