avl树 java_平衡二叉树 AVL树结构详解 [Java实现]

8c29d1ea052aad0910228d5b05f404ce.png

作者

NeroJings

来源

https://blog.csdn.net/zhang6622056/article/details/82698859

本文思维导图

adbf6c8dab063dd4e7dc6ad202b1501c.png

简述

先不说平衡二叉树,我们单开来说,这样比较方便理解。

先说二叉树,再说平衡条件,没那么多花里胡哨的理论,我只是想让大家看完能明白,能写出来。

二叉树

什么是二叉树?二叉树数据结构,顾名思义,只有两个叉,在数据结构中,操作性能要远高于线性结构,有O(height)的索引性能。与线性结构有相同的空间复杂度,特性如下:

每个节点最多只有两个儿子节点左儿子小,右儿子大 (大小按照我们默认的比较规则,本例用int来比较)线行找7与二叉数找7

776965270d238778494f0009a8a12171.gif

线性找7

2bfdde0f1010bd9ade30146fd4708720.gif

二叉树找7 okay,我想大家聪明人已经看出来了,二叉树搜索用了2次,而线性结构却用了5次。

说白了,二叉树结构,我每次问一个节点,都会离着我的目标越来越近,但是线性的则不然,我必须一个个问。

说到这儿,我想会有博友提出质疑了,如果线性查找,7恰好就在第一个呢?那不是一下就找到了吗?

哈哈,你怎么不上天呢 - -。还第一个。开个小玩笑。

这就是二叉树索引的好处。相比看图比码字要清楚的多。

平衡条件

那么,什么叫平衡呢?其实很简单,任何一个节点的子节点高度差必须小于2

6c2d5fb2cae5dda2250994d7ba865b65.png

第一个二叉平衡树

从下往上数,第一个高度为1(比较符合日常生活数数),那我们数数吧5:———1高度 | 4,7,23,71 ———2高度| 6,50 ———3高度 | 15 ———4高度比如节点6,那么4和7的高度都是2,那就2-2 < 2 。平衡!!难点一 递归

4a348a2087a6fae2c78c671abf718bac.gif

递归查找

我又加入了一些节点,方便大家理解递归深度

每一次正向橙色线条的滚动,就是一次递归查找每一次正向橙色线条的滚动,方法的入栈!递归的深度,取决于线条走了几次,那就有多大的栈深度本次查找,刨除root,共4次进栈难点二 回溯

0037ae249fb6dacdd86da193ffa7f902.gif

插入回溯

先不要关心这个旋转操作,如图所示,我们在递归的基础上,沿着线条理解一下回溯

每一次逆向橙色线条的滚动,就是一次回溯操作递归的每一个节点,都会在回溯的轨迹上正因为每一次递归,都有每一次回溯,那么,我们就可以先完成相关操作(增加或删除)之后,判定平衡4种旋转

左左类型旋转

博主尽量放慢了速度,让大家看清楚究竟旋转是如何进行的,这是一个插入操作,我们看到在不平衡的时候,进行了左旋转,这里我们看到

正向插入,递归3-2-1逆向回溯,1-2 判断平衡条件 ,是平衡的再次回溯,2-3,3的左边高度为2,右边没有节点为0,那么2-0 > 1,不平衡!到这里我们基本上理解了平衡的判断,下面正式说一下旋转:

判断不平衡边 在3节点判定,不平衡,那么左边高,我们需要调整左边,获取左边节点2判断旋转类型 这时候我们拿到节点2,判断节点2哪边高。左边高,为左左类型。右边高为左右旋转类型,我们先不管旋转操作 3.left = 2.right; 2.right = 3; 重新计算,2和3节点的高度

右右类型旋转[同上,不再叙述]

左右类型旋转

右左类型旋转

到此旋转就说完了,希望大家好好的理解第一个左左类型!(理解了一个也就都理解了)

后续部分没有讲是因为说太多反而更乱。

后续的理解不了没关系,我们代码在看。

代码基础部分

node类

publicstaticclassAvlNodeInteger{private Integer value;private Integer height;private AvlNodeInteger left;private AvlNodeInteger right;publicAvlNodeInteger(int t){ initNode(t,null,null,1); }publicAvlNodeInteger(int t,AvlNodeInteger left,AvlNodeInteger right){ initNode(t,left,right,null); }privatevoidinitNode(int t,AvlNodeInteger left,AvlNodeInteger right,Integer height){this.setValue(t);this.left = left;this.right = right;this.height = height; }public Integer getValue() {returnvalue; }publicvoidsetValue(Integer value) {this.value = value; }public Integer getHeight() {return height; }publicvoidsetHeight(Integer height) {this.height = height; }public AvlNodeInteger getLeft() {return left; }publicvoidsetLeft(AvlNodeInteger left) {this.left = left; }public AvlNodeInteger getRight() {return right; }publicvoidsetRight(AvlNodeInteger right) {this.right = right; } }高度计算

/**** 求一个节点的高度 * @param t * @return */privateintheight(AvlNodeInteger t){returnnull == t ? 0 : t.getHeight(); }

/*** * 求左右子节点最大高度 * @param left * @param right * @return */privateint maxHeight(AvlNodeInteger left,AvlNodeInteger right){ return height(left) > height(right) ? height(left) : height(right); }插入操作

旋转

/*** * 左左旋转模型 * @param node 旋转之前的parent node 节点 * @return 旋转之后的parent node节点 */private AvlNodeInteger leftLeftRotate(AvlNodeInteger node){ AvlNodeInteger newRoot = node.getLeft(); node.setLeft(newRoot.getRight()); newRoot.setRight(node);//由此node的高度降低了,newRoot的高度提高了。//newRoot的高度由node的高度而来 node.setHeight(maxHeight(node.getLeft(),node.getRight())+1); newRoot.setHeight(maxHeight(newRoot.getLeft(),newRoot.getRight())+1);return newRoot; }/*** * 右右旋转模型 * @param node * @return */private AvlNodeInteger rightRightRotate(AvlNodeInteger node){ AvlNodeInteger newRoot = node.getRight(); node.setRight(newRoot.getLeft()); newRoot.setLeft(node);//由此node的高度降低了,newRoot的高度提高了。//newRoot的高度由node的高度而来 node.setHeight(maxHeight(node.getLeft(),node.getRight())); newRoot.setHeight(maxHeight(newRoot.getLeft(),newRoot.getRight()));return newRoot; }/** * 左右模型,先右右,再左左 * @param node * @return */private AvlNodeInteger leftRightRotate(AvlNodeInteger node){//注意传递的参数 node.setLeft(rightRightRotate(node.getLeft()));return leftLeftRotate(node); }/*** * 右左模型,先左左,在右右 * @param node * @return */private AvlNodeInteger rightLeftRotate(AvlNodeInteger node){ node.setRight(leftLeftRotate(node.getRight()));return rightRightRotate(node); }insert

/**** * 对外开放,插入操作 * @param val * @throws Exception */public void insert(Integer val) throws Exception {if(null == root){ initRoot(val); size++;return; }if(contains(val)) thrownewException("The value is already exist!"); insertNode(this.root,val); size++; }/** * 递归插入 * parent == null 到最底部插入前节点判断情况 * @param parent * @param val * @return */private AvlNodeInteger insertNode(AvlNodeInteger parent,Integer val){if(parent == null){return createSingleNode(val); }if(val < parent.getValue()){ //插入判断,小于父节点,插入到右边//注意理解回溯,这里最终返回的是插入完成节点//每一层回溯,都会返回相应当时递归的节点!!!parent.setLeft(insertNode(parent.getLeft(),val));//判断平衡,不要在意这里的parent是谁,//这个parent肯定是递归层级上,回溯的一个节点!每一个节点都需要判断平衡if(height(parent.getLeft()) - height(parent.getRight()) > 1){ Integer compareVal = (Integer) parent.getLeft().getValue();//左左旋转类型if(val < Integer.valueOf(compareVal)){parent = leftLeftRotate(parent); }else{ //左右旋转类型parent = leftRightRotate(parent); } } }if(val > parent.getValue()){ //插入判断,小于父节点,插入到右边//注意理解回溯,这里最终返回的是插入完成节点//每一层回溯,都会返回相应当时递归的节点!!!parent.setRight(insertNode(parent.getRight(),val));//判断平衡,不要在意这里的parent是谁,//这个parent肯定是递归层级上,回溯的一个节点!每一个节点都需要判断平衡if(height(parent.getRight()) - height(parent.getLeft()) > 2){ Integer compareVal = (Integer) parent.getLeft().getValue();if(val > compareVal){parent = rightRightRotate(parent); }else{parent = rightLeftRotate(parent); } } }parent.setHeight((maxHeight(parent.getLeft(),parent.getRight()))+1);returnparent; }删除操作

public void remove(Integer val) {if(null == val || null == root){return;}if(!contains(val)){return; } remove(root,val); }/**** * AVL删除,平衡树实现 * @param parent * @param val * @return */private AvlNodeInteger remove(AvlNodeInteger parent,Integer val){if(val < parent.getValue()){ //左子树递归查询//删除以后返回替换的新节点 AvlNodeInteger newLeft = remove(parent.getLeft(),val);parent.setLeft(newLeft);//检查是否平衡,删除的左边,那么用右边-左边if(height(parent.getRight()) - height(parent.getLeft()) > 1){ AvlNodeInteger tempNode = parent.getRight();if(height(tempNode.getLeft()) > height(tempNode.getRight())){ //RL类型 rightLeftRotate(parent); }else{ //RR类型 rightRightRotate(parent); } } }elseif(val > parent.getValue()){ //右子树递归查找//删除以后返回替换的新节点 AvlNodeInteger newRight = remove(parent.getRight(),val);parent.setRight(newRight);//检查是否平衡if(height(parent.getLeft()) - height(parent.getRight()) > 1){ AvlNodeInteger tempNode = parent.getLeft();if(height(tempNode.getLeft()) > height(tempNode.getRight())){ //LL类型 leftLeftRotate(parent); }else{ //LR类型 leftRightRotate(parent); } } }else{ //相等,匹配成功if(null != parent.getLeft() && null != parent.getRight()){ //左右子节点都不为空//判断高度,高的一方,拿到最大(左),最小(右)的节点,作为替换节点。//删除原来匹配节点//左边更高,获取到左边最大的节点if(parent.getLeft().getHeight() > parent.getRight().getHeight()){ AvlNodeInteger leftMax = getMax(parent.getLeft());parent.setLeft(remove(parent.getLeft(),leftMax.getValue())); leftMax.setLeft(parent.getLeft()); leftMax.setRight(parent.getRight()); leftMax.setHeight(maxHeight(leftMax.getLeft(),leftMax.getRight()));parent = leftMax; }else{ //右边更高,获取到右边最小的节点 AvlNodeInteger rightMin = getMin(parent.getRight());parent.setRight(remove(parent.getRight(),rightMin.getValue())); rightMin.setLeft(parent.getLeft()); rightMin.setRight(parent.getRight()); rightMin.setHeight(maxHeight(parent.getLeft(),parent.getRight())+1);parent = rightMin; } }else{//有任意一方节点为空,则不为空的那一方作为替换节点,删除原来的节点parent = null; } }returnparent; }/*** * 删除时用到,获取当前节点子节点最大值 * @param currentRoot * @return */private AvlNodeInteger getMax(AvlNodeInteger currentRoot){if(currentRoot.getRight() != null){ currentRoot = getMax(currentRoot.getRight()); }return currentRoot; }/*** * 删除时用到,获取当前节点子节点最小值 * @param currentRoot * @return */private AvlNodeInteger getMin(AvlNodeInteger currentRoot){if(currentRoot.getLeft() != null){ currentRoot = getMin(currentRoot.getLeft()); }return currentRoot; }

以上就是难点插入和删除的实现了, 没有过多阐述,是因为大家如果真的理解了上面说明的理论, 那么应该没有问题来理解这些code。

当然有任何问题大家可以在留言区回复我 ,欢迎大家指正!

4种遍历

前序遍历 根左右中序遍历 左跟右后序遍历 左右根层级遍历 从root开始,一层层

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值