平衡二叉树

Posted rookiejw

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了平衡二叉树相关的知识,希望对你有一定的参考价值。

why? what? when? how?

最近几天看了某个大佬写的学习总结,觉得这个方式不错就引进了。

why

为什么要用平衡二叉树?

二叉搜索树

二叉搜索树的查找效率和 BST 建立的时候节点输入顺序相关。

1. 若输入节点顺序 1、2、3、4、5

技术分享图片

树的深度为 5,查找效率 O(N),平均查找长度 ASL = ( 1+2+3+4+5 ) / 5 = 3

2. 若输入节点顺序 4、3、5、1、2

技术分享图片

该树是完全二叉树,树的深度是 3,查找效率是 O(log2n), ASL = ( 1+22+32 ) / 5 = 2.1


因为 BST 的查询效率和节点输入的顺序有很大联系最坏情况是 O(N),为了提高查询效率所以平衡二叉树(查找的时间复杂度 O(log2N) —— 证明见定义后面)出现了。


what

什么是平衡二叉树?

平衡二叉树定义

平衡因子

平衡因子( Balance Factor,简称 BF ):BF(T)=hL-hR,其中hL和hR分别为T的左、右子树的高度。

定义

平衡二叉树 ( Balanced Binary Tree ) ( AVL树 ) 空树,或者任一结点左、右子树高度差的绝对值不超过1,即 |BF(T)| <= 1 .


平衡二叉树的高度能达到 log2N 吗? 如果能到达那么相比 BST 查询效率会高很多。

证明:设nh高度为h的平衡二叉树的最少结点数。结点数最少时:

若结点一个 1 个的时候树高为 0(我上面是为 1 设为 0 主要是为了下面好看些)

当树高为 0: 最少结点数 1

当树高为 1: 最少结点数 2

当树高为 2: 最少结点数 4

当树高为 3: 最少结点数 7

技术分享图片

技术分享图片

这个和斐波那契数列(Fibonacci sequence)有点像

技术分享图片

技术分享图片


when

什么时候调整平衡二叉树(插入删除满足什么样的状态时)?

当插入或删除时 |BF(T)| > 1。


how

如何调整平衡二叉树?

平衡二叉树的调整

LL,RR,LR,RL 旋转

以前学的时候对 LL,RR,LR,RL 旋转不是很理解为什么这样叫?

LL 旋转

技术分享图片

插入结点 2 后

技术分享图片

不平衡了,“发现者”是结点4,“麻烦结点" 2 在结点 4的左边的左边。

旋转调整

技术分享图片

“发现者”是结点4,“麻烦结点" 2 在结点 4的左子树的左边叫 LL 插入,需要 LL 旋转(往右转一下)。


RR 旋转

技术分享图片

旋转调整

技术分享图片

"麻烦结点" 6 在"发现者" 4 的右子树的右边叫 RR 插入,需要 RR 旋转(往左转一下)。


LR旋转

技术分享图片

旋转调整

技术分享图片

"麻烦结点" 4 在"发现者" 6 左子树的右边所以叫 LR 插入,需要 LR 旋转(先右转再左转)。


RL旋转

技术分享图片

旋转调整

技术分享图片

"麻烦结点" 5 在"发现者" 2 右子树的左边所以叫 RL 插入,需要 RL 旋转(先左转再右转)。


实现

结点定义

class AVLNode{
Integer data; //存放数据
AVLNode leftNode;   //左孩子
AVLNode rightNode;  //右孩子
int height;     //树高
}

四种旋转

LL

技术分享图片

    //右单旋转
public AVLNode singleRightRotation(AVLNode node) {
    //node 有左孩子
    //将node 和其左孩子右转,并更新高度,返回新的根节点
    AVLNode temp = node.leftNode;
    node.leftNode = temp.rightNode;
    temp.rightNode = node;
    node.height = max(getHeight(node.leftNode), getHeight(node.rightNode)) + 1;
    temp.height = max(getHeight(temp.leftNode), node.height) + 1;
    return temp;
}

RR

技术分享图片

public AVLNode singleLeftRotation(AVLNode node) {
    //node 有右孩子
    //将node 和其右孩子左转,并更新高度,返回新的根节点
    AVLNode temp = node.rightNode;
    node.rightNode = temp.leftNode;
    temp.leftNode = node;
    node.height = max(getHeight(node.leftNode), getHeight(node.rightNode)) + 1;
    temp.height = max(getHeight(temp.rightNode), node.height) + 1;
    return temp;
}

LR

技术分享图片

public AVLNode doubleLeftRightRotation(AVLNode node){
    //node有个左孩子 B,B有其右孩子
    //B、C左转,C、node右转
    //B C 左单转
    node.leftNode=singleLeftRotation(node.leftNode);
    //C node 右单转
    return singleRightRotation(node);
}

RL

技术分享图片

    public AVLNode doubleRightLeftRotation(AVLNode node){
    //node 有个右孩子 B,B有其左孩子
    //B C 右转,c node 左转
    //B C 右单转
    node.rightNode=singleRightRotation(node.rightNode);
    //C node 左单转
    return singleLeftRotation(node);
}

插入

//插入结点
public AVLNode avlInsert(AVLNode node, int val) {
    if (node == null) {
        node = new AVLNode();
        node.data = val;
        node.leftNode = node.rightNode = null;
    }
    //插入左边
    if (val < node.data) {
        node.leftNode = avlInsert(node.leftNode, val);
        if (getHeight(node.leftNode) - getHeight(node.rightNode) == 2) {
            //LL
            if (val < node.leftNode.data) {
                node = singleRightRotation(node);
            } else {
                //LR
                node = doubleLeftRightRotation(node);
            }
        }
    } else if (val > node.data) {
        node.rightNode = avlInsert(node.rightNode, val);
        if (getHeight(node.leftNode) - getHeight(node.rightNode) == -2) {
            //RR
            if (val > node.rightNode.data) {
                node = singleLeftRotation(node);
            } else {
                //RL
                node = doubleRightLeftRotation(node);
            }
        }
    } else {
        //相等不需要任何操作
    }
    //更新树高
    node.height = max(getHeight(node.leftNode), getHeight(node.rightNode)) + 1;
    return node;
}

删除

删除是平衡二叉树操作中最繁琐的步骤了需要核查删除结点父亲结点及以上父亲结点是否平衡。不过具体情况可以分为 3 种。

  1. 删除的结点是叶子节点
  2. 删除的结点含有左孩子或右孩子
  3. 删除的结点含有左孩子和右孩子

当删除结点后需要检索推算父结点或祖先结点是否失衡!!!

public AVLNode avlDelete(AVLNode node,int val){
    //指向前驱、后继(当删除结点有左孩子和右孩子)
    AVLNode pre,post;
    //没有找到要删除的结点
    if(node==null){
        System.out.println("没有找到要删除的结点");
    }else if(node.data==val){
        //需要删除的结点是叶子节点
        if(node.leftNode==null&&node.rightNode==null){
            node=null;
        }else if(node.rightNode==null){
            //当要删除的结点右子树为空时
            node=node.leftNode;
        }else if(node.leftNode==null){
            //当要删除的结点左子树为空时
            node=node.rightNode;
        }else{
            //左右子树都不为空时

            //删除前驱(中序遍历)
            pre=node.leftNode;
            while(pre.rightNode!=null) {
                pre = pre.rightNode;
            }
            node.data=pre.data;
            //为什么要用node.leftNode,因为node.data==pre.data
            node.leftNode=avlDelete(node.leftNode,pre.data);
            //重新判断树高
            node.height=max(getHeight(node.leftNode),getHeight(node.rightNode))+1;
            //判断是否需要调整
            if(-2==getHeight(node.leftNode)-getHeight(node.rightNode)){
                if(getHeight(node.rightNode.leftNode)>getHeight(node.rightNode.rightNode)){
                    //RL旋转
                    node=doubleRightLeftRotation(node);
                }else{
                    //RR旋转
                    node=singleLeftRotation(node);
                }
            }
//                //删除后继(中序遍历)
//                post=node.rightNode;
//                while(post.leftNode!=null){
//                    post=post.leftNode;
//                }
//                node.data=post.data;
//                node.rightNode=avlDelete(node.rightNode,post.data);
//                node.height=max(getHeight(node.leftNode),getHeight(node.rightNode))+1;
//                if(2==getHeight(node.leftNode)-getHeight(node.rightNode)){
//                    if(getHeight(node.leftNode.leftNode)>getHeight(node.leftNode.rightNode)){
//                        //LL旋转               
//                        node=singleRightRotation(node);
//                    }else{
//                        //LR旋转                
//                        node=doubleLeftRightRotation(node);
//                    }
//                }
        }
    }
    //在左子树中递归删除
    else if(val<node.data){
        node.leftNode=avlDelete(node.leftNode,val);
        node.height=max(getHeight(node.leftNode),getHeight(node.rightNode))+1;
        //判断是否需要进行旋转以保持二叉平衡树的特性
        if(-2==getHeight(node.leftNode)-getHeight(node.rightNode)){
            if(getHeight(node.rightNode.leftNode)>getHeight(node.rightNode.rightNode)){
                //RL旋转
                node=doubleRightLeftRotation(node);
            }else{
                //RR旋转
                node=singleLeftRotation(node);
            }
        }
    }
    //在右子树中递归删除
    else {
        node.rightNode=avlDelete(node.rightNode,val);
        node.height=max(getHeight(node.leftNode),getHeight(node.rightNode))+1;
        //判断是否需要进行旋转以保持二叉平衡树的特性
        if(2==getHeight(node.leftNode)-getHeight(node.rightNode)){
            if(getHeight(node.leftNode.leftNode)>getHeight(node.leftNode.rightNode)){
                //LL旋转
                node=singleRightRotation(node);
            }else{
                //LR旋转
                node=doubleLeftRightRotation(node);
            }
        }
    }
    return node;
}

结果

技术分享图片


完整代码

https://github.com/rookieLJ/AVLTree.git

总结

参考
陈越 何钦铭 数据结构
https://www.cnblogs.com/Camilo/p/3917041.html

平衡二叉树是建立在二叉搜索树基础上为了保存查找效率 O(log2N)查找次数小于等于树高,但是插入和删除结点会调整树结构所以插入和删除的效率无法保证。

使用 what? why? when? how?学习总结感觉效果非常好,思路清晰很多十分推荐!!!!

才学疏浅,有什么问题请大家指出来。十分感谢!

以上是关于平衡二叉树的主要内容,如果未能解决你的问题,请参考以下文章

求数据结构算法平衡二叉树实现代码

平衡二叉树详解——PHP代码实现

PHP代码实现平衡二叉树详解

判断一颗二叉树是否为二叉平衡树 python 代码

树--07---二叉树--04--平衡二叉树(AVL树)

平衡二叉树的定义及基本操作(查找插入删除)及代码实现