草庐IT

平衡二叉树

清风拂来水波不兴 2023-04-08 原文

1.定义

平衡二叉树,又称AVL树,用于解决二叉排序树高度不确定的情况,如果二叉排序树的子树间的高度相差太大,就会让二叉排序树操作的时间复杂度升级为O(n),为了避免这一情况,为最坏的情况做准备,就出现了平衡二叉树,使树的高度尽可能的小,其本质还是一棵二叉搜索树。

平衡二叉树的性质

  • 左子树和右子树的高度之差的绝对值小于等于1
  • 左子树和右子树也是平衡二叉树

为了方便起见,给树上的每个结点附加一个数字,给出该结点左子树与右子树的高度差,这个数字称为结点的平衡因子(BF)

平衡因子=结点左子树的高度-结点右子树的高度。

因此平衡二叉树所有结点的平衡因子只能是-1、0、1,如下图,是一个平衡二叉树

平衡二叉树的高度为logn

2.失衡

当我们在一个平衡二叉树上插入一个结点时,有可能会导致失衡,即出现平衡因子绝对值大于1       的结点,如下图,当插入结点后,其中key为53的结点失去了平衡,我们称该结点为失衡结点,我们必须重新调整树的结构,使之恢复平衡。

 不一定只有一个结点失去平衡,有可能插入一个结点会让多个结点失衡。这时候找 最小的失衡子树的根节点作为失衡结点

3.恢复平衡

那如何去恢复平衡,使得二叉搜索树依然成立为一棵平衡树?先来看平衡调整的四种类型:

 举个例子:如第一个,当平衡二叉树为AB时,插入一个C结点,使得失衡了,失衡结点为A,此时因为C结点插入的位置为失衡结点的左孩子的左孩子,所以是LL型,以此类推。

为了恢复平衡,针对每一种都不一样,但目的都是调整为平衡的,且不能违背二叉搜索树的性质:如下图是每种失衡类型的解决方法,每个都不太一样,目的都是一样的,把key的值为中等的变为树的根,最小的放在左孩子,做大的放右孩子,通过这一目的,降低树的高度,也不用死记硬背。

如书上所说,这一操作被称为树的旋转,如LL型被称为右旋,RR型称为左旋,LR型是先左旋,再右旋,RL型先右旋再左旋。

3.1LL型调整

如下,在实际情况中,调整的内容可能看起来更复杂,如下方块的表示省略了n个结点,调整的方式如下(右旋):

步骤为:

  • B结点带左子树(α和新添加的C结点)一起上升
  • A结点成为B的右子树
  • 原来的B的右子树成为A的左子树,因为A的左子树是B,上升了,所以空着的

可以看成是A右旋为B的右子树

3.2RR型

LL型和RR型是最简单的几种情况,所以放在了前面。RR型即插入的结点位置在失衡结点的右子树的右子树中,如下图:

 调整的步骤和LL的差不多

步骤为:

  • B结点和它的右子树(β和新添加的C结点)一起上升
  • A结点变为B结点的左子树
  • 原来B的左子树α变为A的右子树

可以看成是A左旋至B的左子树

3.3LR型调整

LR型的跳转如下(左旋再右旋):

  • 首先让B和它的子树(除了C)左旋至C的左子树,把C为根的树接入A的左子树
  • 然后让A右旋,成为C的右子树 

其过程就是把中位数的C上升,成为AB的双亲

3.4RL型调整

RL型如下(先右旋再左旋):

  • 首先让B和它的子树(除了C)右旋至C的右子树,把C为根的树接入A的右子树
  • 然后让A左旋,成为C的左子树 

和LR差不多

例题:输入关键字序列(16,3,7,11 ,9,26,18,14,15)给出AVL树

参考答案:

4.代码实现

public class AVLTree {
    private AVLNode root;

    public class AVLNode {
        public int data;
        public int height;
        public AVLNode parent;
        public AVLNode left;
        public AVLNode right;

        public AVLNode(int data) {
            this.data = data;
            this.height = 1;
        }

        @Override
        public String toString() {
            return "AVLNode{" +
                    "data=" + data +
                    '}';
        }

        public void inOrder() {//中序遍历
            if (this.left != null) {
                this.left.inOrder();
            }
            System.out.println(this);
            if (this.right != null) {
                this.right.inOrder();
            }
        }
    }

    private int calcHeight(AVLNode root) {
        if (root.left == null && root.right == null) {
            return 1;
        } else if (root.right == null) {
            return root.left.height + 1;
        } else if (root.left == null) {
            return root.right.height + 1;
        } else {
            return root.left.height > root.right.height ? root.left.height + 1 : root.right.height + 1;
        }
    }

    private int calcBF(AVLNode root) {
        if (root == null) {
            return 0;
        } else if (root.left == null && root.right == null) {
            return 0;
        } else if (root.right == null) {
            return root.left.height;
        } else if (root.left == null) {
            return -root.right.height;
        } else {
            return root.left.height - root.right.height;
        }
    }

    public AVLNode leftRotate(AVLNode root) {
        AVLNode oldRoot = root;
        AVLNode newRoot = root.right;
        AVLNode parent = root.parent;
        //1.newRoot 替换 oldRoot 位置
        if (null != parent) {
            if (oldRoot.parent.data > oldRoot.data) {
                parent.left = newRoot;
            } else {
                parent.right = newRoot;
            }
        }
        newRoot.parent = parent;
        //2.重新组装 oldRoot (将 newRoot 的左子树 给 oldRoot 的右子树)
        oldRoot.right = newRoot.left;
        if (newRoot.left != null) {
            newRoot.left.parent = oldRoot;
        }
        //3. oldRoot 为 newRoot 的左子树
        newRoot.left = oldRoot;
        oldRoot.parent = newRoot;
        //刷新高度
        oldRoot.height = calcHeight(oldRoot);
        newRoot.height = calcHeight(newRoot);
        return newRoot;
    }


    public AVLNode rightRotate(AVLNode root) {
        AVLNode oldRoot = root;
        AVLNode newRoot = root.left;
        AVLNode parent = root.parent;
        //1.newRoot 替换 oldRoot 位置
        if (null != parent) {
            if (oldRoot.parent.data > oldRoot.data) {
                parent.left = newRoot;
            } else {
                parent.right = newRoot;
            }
        }
        newRoot.parent = parent;
        //2.重新组装 oldRoot (将 newRoot 的右子树 给 oldRoot 的左子树)
        oldRoot.left = newRoot.right;
        if (newRoot.right != null) {
            newRoot.right.parent = oldRoot;
        }
        //3. oldRoot 为 newRoot 的左子树
        newRoot.right = oldRoot;
        oldRoot.parent = newRoot;
        //刷新高度
        oldRoot.height = calcHeight(oldRoot);
        newRoot.height = calcHeight(newRoot);
        return newRoot;
    }

    public void insert(int data) {
        if (null == this.root) {
            this.root = new AVLNode(data);
            return;
        }
        this.root = insert(this.root, data);
    }

    public AVLNode insert(AVLNode root, int data) {
        //插入左子树
        if (data < root.data) {
            if (null == root.left) {
                root.left = new AVLNode(data);
                root.left.parent = root;
            } else {
                insert(root.left, data);
            }
        }
        //插入右子树
        else if (data > root.data) {
            if (null == root.right) {
                root.right = new AVLNode(data);
                root.right.parent = root;
            } else {
                insert(root.right, data);
            }
        }
        //刷新高度
        root.height = calcHeight(root);
        //旋转
        //1. LL 型 右旋转
        if (calcBF(root) == 2) {
            //2. LR 型 先左旋转
            if (calcBF(root.left) == -1) {
                root.left = leftRotate(root.left);
            }
            root = rightRotate(root);
        }
        //3. RR型 左旋转
        if (calcBF(root) == -2) {
            //4. RL 型 先右旋转
            if (calcBF(root.right) == 1) {
                root.right = rightRotate(root.right);
            }
            root = leftRotate(root);
        }

        return root;
    }

    public void inOrder() {
        root.inOrder();
    }
}

测试:

AVLTree tree = new AVLTree();
tree.insert(16);
tree.insert(3);
tree.insert(7);
tree.insert(11);
tree.insert(9);
tree.insert(26);
tree.insert(18);
tree.insert(14);
tree.insert(15);

tree.inOrder();

结果如下: 

AVLNode{data=3}
AVLNode{data=7}
AVLNode{data=9}
AVLNode{data=11}
AVLNode{data=14}
AVLNode{data=15}
AVLNode{data=16}
AVLNode{data=18}
AVLNode{data=26}

就是上面例题中的平衡二叉树。

有关平衡二叉树的更多相关文章

  1. ruby - 匹配未转义的平衡定界符对 - 2

    如何匹配未被反斜杠转义的平衡定界符对(其本身未被反斜杠转义)(无需考虑嵌套)?例如对于反引号,我试过了,但是转义的反引号没有像转义那样工作。regex=/(?!$1:"how\\"#expected"how\\`are"上面的正则表达式不考虑由反斜杠转义并位于反引号前面的反斜杠,但我愿意考虑。StackOverflow如何做到这一点?这样做的目的并不复杂。我有文档文本,其中包括内联代码的反引号,就像StackOverflow一样,我想在HTML文件中显示它,内联代码用一些spanMaterial装饰。不会有嵌套,但转义反引号或转义反斜杠可能出现在任何地方。

  2. ruby - 在 Ruby 中实现二叉树 - 2

    我一直在尝试在Ruby中实现BinaryTree类,但我得到了stackleveltoodeep错误,尽管我似乎没有在该特定代码段中使用任何递归:1.classBinaryTree2.includeEnumerable3.4.attr_accessor:value5.6.definitialize(value=nil)7.@value=value8.@left=BinaryTree.new#stackleveltoodeephere9.@right=BinaryTree.new#andhere10.end11.12.defempty?13.(self.value==nil)?true:

  3. ruby - 检查字符串是否有平衡括号 - 2

    我目前正在做一个Ruby问题测验,但我不确定我的解决方案是否正确。运行检查后,它显示编译成功,但我只是担心这不是正确的答案。问题:AstringSconsistingonlyofcharacters'('and')'iscalledproperlynestedif:Sisempty,Shastheform"(U)"whereUisaproperlynestedstring,Shastheform"VW"whereVandWareproperlynestedstrings.Forexample,"(()(())())"isproperlynestedand"())"isn't.Write

  4. ruby - 使用递归正则表达式(如 perl)在 Ruby 中匹配平衡括号 - 2

    我一直在寻找一种方法来匹配正则表达式中的平衡括号,并在Perl中找到了一种使用递归正则表达式的方法:my$re;$re=qr{\((?:(?>[^()]+)#Non-parenswithoutbacktracking|(??{$re})#Groupwithmatchingparens)*\)}x;来自perlregularexpressionsite.有没有办法用Ruby或类似的语言做到这一点?更新:对于那些感兴趣的人,这里有一些有趣的链接:Onigurumamanual-来自Sawa的回答。PragmaticProgrammers'Ruby1.9RegularExpressionsS

  5. javascript - 如何通过谷歌负载平衡使用 socket.io - 2

    我们在尝试通过googleload将socket.io连接到node.jscomputeengine实例时遇到一些问题平衡。如果我从我的浏览器直接连接到我的node.js的外部IP一切正常。如果我尝试通过负载平衡(这将是生产架构)连接到相同的node.js,socket一直断开连接。我们尝试使用sessionAffinity配置负载平衡但没有成功。有什么建议吗?谢谢 最佳答案 默认情况下,http负载平衡的超时设置默认为30秒(Source),这适用于web套接字,当后端支持该协议(protocol)时,它又被socket.io使用

  6. javascript - 与二维碰撞有关的四叉树 - 2

    我一直在研究这个:https://github.com/mikechambers/ExamplesByMesh/blob/master/JavaScript/QuadTree/src/QuadTree.js我相信我理解四叉树的一般概念,尽管我对它们的工作原理和上面的实现有两个问题:难道你不需要每隔几毫秒重建整个树吗?在Javascript中,这不会非常慢吗?如果我有这样的东西:http://davzy.com/screenshots/skitched-20120318-180324.png,那么很容易找到同一个四边形中的其他点,但我有一个矩形与3个不同的四边形相交,有没有办法让它显示为

  7. 数据结构之线索二叉树详细解释 - 2

    1.1线索二叉树的原理我们现在倡导节约型社会,一切都应该以节约为本。但当我们创建二叉树时我们会发现其中一共有两个指针域,有的指针域指向的结构为空,这也就浪费了很多空间。所以为了不去浪费这些空间我们采取了一个措施。就是利用那些空地址,存放指向结点在某种遍历次序之下的前驱和后继结点的地址。就好像GPS导航仪一样,它可以告诉我们下一站是哪里,我们是从那里来的。我们把这种指向前驱和后继的指针成为线索,加上线索的二叉链表称为线索链表,相应的二叉树就成为线索二叉树。我们将对二叉树以某种次序遍历使其变为线索二叉树的过程称为线索化。下图是线索化结束的图:这里存在一个问题,我们怎么知道某一个结点的lchild是

  8. javascript - 如何匹配 javascript 正则表达式中的平衡定界符? - 2

    我本来以为这个问题是不可能的;据我所知,Javascript的正则表达式风格既没有递归插值,也没有漂亮的.NET平衡组功能。然而它就在那里,作为regex.alf.nu上的问题12|:匹配的平衡对和>.除非集合中有其他模式,否则我不会得到。那么……这可能吗?如果是,怎么办?注意事项:我知道这对于真正的正则表达式来说是不可能的,但基于挑战,它似乎在Javascript的风格中是可能的(它至少不规则到足以有反向引用)。我只是不知道有什么功能可以让他们这样做。没有其他代码-该表单允许输入单个正则表达式,该正则表达式根据页面上的测试字符串进行评估。我想我可以尝试破解页面以打破正则表达式并进入原

  9. 【树】你真的会二叉树了嘛? --二叉树LeetCode专题Ⅳ - 2

     Halo,这里是Ppeua。平时主要更新C语言,C++,数据结构算法......感兴趣就关注我吧!你定不会失望。🌈个人主页:主页链接🌈算法专栏:专栏链接     我会一直往里填充内容哒!🌈LeetCode专栏:专栏链接     目前在刷初级算法的LeetBook。若每日一题当中有力所能及的题目,也会当天做完发出🌈代码仓库:Gitee链接🌈点击关注=收获更多优质内容🌈目录题目:111. 二叉树的最小深度题解:代码实现:题目:700. 二叉搜索树中的搜索题解:代码实现:题目:701. 二叉搜索树中的插入操作题解:代码实现:题目:450. 删除二叉搜索树中的节点题解:代码实现:完结撒花:人生苦短,

  10. go - 在golang中的表达式中检查括号是否平衡[保持] - 2

    给定表达式字符串exp,编写程序检查exp中“{”、“}”、“(”、“)”、“[”、“]的对和顺序是否正确。packagemainimport("fmt"stack"github.com/golang-collections/collections/stack")funcmain(){s:="(a[0]+b[2c[6]])){24+53}"stackO:=stack.New()stackmap:=map[string]string{"[":"]","(":")","{":"}"}varstr=""for_,num:=ranges{str=string(num)if(str=="{"||

随机推荐