网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
case RH:// 单左旋的
T.setBf(EH);
lc.setBf(EH);
L_Rotate(T);
break;
case LH:// 先右旋,再左旋的
BSTNode rd = lc.getLchild();
switch (rd.getBf()) {
case LH:
T.setBf(EH);
lc.setBf(RH);
break;
case EH:
T.setBf(EH);
lc.setBf(EH);
break;
case RH:
T.setBf(LH);
lc.setBf(EH);
break;
}
rd.setBf(EH);
R_Rotate(lc);
L_Rotate(T);
}
}
### 6.结点插入的实现
结点的插入是通过递归实现的,代码如下,可以参考一下最好搞懂,因为删除的递归实现和这个很类似。
首先我定义一个静态变量taller,记录当前的树是否长高,当taller=false时树的平衡就调整好了。
private static boolean taller;
public boolean insertAVL(BSTNode T, int x) {// 以T为根节点的平衡二叉树中插入元素x,成功返回true,失败返回false,taller反应树是否长高
if (T == null) {// 只有刚开始插入树为空才会执行这一步
root = new BSTNode(x);
return true;
}
if (x == T.getData()) {// ①
taller = false;
return false;
} else if (x < T.getData()) {// ②
if (T.getLchild() == null) {// 检查左子树
BSTNode q = new BSTNode(x);
T.setLchild(q);// 直接插入
q.setParent(T);
switch (T.getBf()) {
case EH:
T.setBf(LH);
taller = true;
break;
case RH:
T.setBf(EH);
taller = false;
break;
}
return true;
}
if (!insertAVL(T.getLchild(), x)) {
return false;
}
if (taller) {
switch (T.getBf()) {// 检查结点的平衡因子
case LH:// 原本结点的左子树比右子树高,且在左子树中插入了,需要做左平衡处理,处理之后树的高度不变
InLeftBalance(T);
taller = false;
break;
case EH:// 左右子树同样高,在左子树中插入,只是树变高了、平衡因子变为1,但当前不用做平衡处理
T.setBf(LH);
taller = true;
break;
case RH:// 右子树比左子树高,在左子树中插入,树的高度不变,当前结点的平衡因子变为0
T.setBf(EH);
taller = false;
break;
}
}
} else {// ③
if (T.getRchild() == null) {// 检查右子树
BSTNode q = new BSTNode(x);
T.setRchild(q);
q.setParent(T);
switch (T.getBf()) {
case EH:
T.setBf(RH);
taller = true;
break;
case LH:
T.setBf(EH);
taller = false;
break;
}
return true;
}
if (!insertAVL(T.getRchild(), x)) {
return false;
}
if (taller) {
switch (T.getBf()) {
case LH:
T.setBf(EH);
taller = false;
break;
case EH:
T.setBf(RH);
taller = true;
break;
case RH:
InRightBalance(T);
taller = false;
break;
}
}
}
return true;
}
## 二、AVL树的删除
### 1.删除节点的分类
由于在书上和其他博主那里都没有找到关于删除的详细讲解,所以我在这里会详细讲一下关于结点的删除。结点的删除我先定义了一个方法deleteAVL(BSTNode T,int x)——在以T为根结的树中删除结点元素值为x的结点。
结点的删除该开始可以分为四种情况,但最后我会将它归结为一种情况。
1.所删除的结点为叶子结点,可以将其直接删除,然后再依次向上调整使整棵树平衡。
2.删除的结点只有左子树且只有左子树一个节点(左右子树的高度差不超过1)。将待删除的结点与它的左子树结点值交换,删除叶子结点。这就转换成了第一种情况。
例如下面这段代码,T就是我们要删除的结点:
deleteAVL(T,x){
******
int value=T.getLchild().getDate();
deleteAVL(T,value);
T.setData(value);
******
}
这样就转换成了第一种情况了,代码只是形式,掌握思想就可以了。
3.删除的结点只有右子树且只有右子树一个节点,这样的情况以及转化方法和情况2几 乎是一样的,同理可以转化成情况1。
4.删除的节点左右子树都不空,这种情况和2.3的思想也是一样的。我们可以找到删除结点的前驱或者后继(中序遍历),前驱的位置是从该结点左转然后一直向右走到尽头(后继是从该节点右转然后向左走到尽头)。通过和2中类似的方法交换,将删除的结点与前驱结点元素值交换,再删除前驱结点。这里要删除的前驱位置结点可能会有左子树结点,那就再交换一次。情况四最多只需要两次交换。然后都能转化成情况1。这里可能看的有点迷糊,不过很正常,到时候看一下代码跟着代码走一次就全都明白了。
### 2.平衡旋转
###### (1)
若根节点A的平衡因子为0,无论删除结点导致A的左子树或右子树高度改变,此时A的平衡因子会变为-1或者1,但A还是平衡的并且以A为根结点的树高度没有发生变化。这种情不用做任何旋转。
###### (2)
若A的平衡因子为1,删除导致左子树的高度减小。此时A的平衡因子变为0,以A为根结点的的树高度减小,当前不用平衡调整。
###### (3)
若A的平衡因子为1,删除导致右子树的高度减小。此时A的平衡因子变为2,这样就需要平衡旋转了。这种情况下的平衡旋转还要依据A的左孩子结点B的平衡因子分为3种:
1. B.getBf()=1,删除后经过旋转树的高度会减小。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190416002722314.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI5NTkwMzU1,size_16,color_FFFFFF,t_70)
2. B.getBf()=0,删除后经过旋转树的高度恢复为原来的高度。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190416002900157.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI5NTkwMzU1,size_16,color_FFFFFF,t_70)
3. B.getBf()=-1,删除后经过旋转树的高度会减小。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190416003138761.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI5NTkwMzU1,size_16,color_FFFFFF,t_70)
###### (4)
若A的平衡因子为-1,删除导致A的左子树高度减少。此时A的平衡因子变为-2,这样就需要平衡旋转了。这种情况下的平衡旋转和(3)是镜像的,要依据A的右孩子结点B的平衡因子分为3种:
1. B.getBf()=1,删除后经过旋转树的高度会减小。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190416010418567.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI5NTkwMzU1,size_16,color_FFFFFF,t_70)
2. B.getBf()=0,删除后经过旋转树的高度恢复为原来的。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190416010453628.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI5NTkwMzU1,size_16,color_FFFFFF,t_70)
3. B.getBf()=-1,删除后经过旋转树的高度会减小。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190416010603433.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI5NTkwMzU1,size_16,color_FFFFFF,t_70)
###### (5)
若A的平衡因子为-1,删除导致A的右子树高度减少。此时A的平衡因子变为0,当前不用进行平和衡旋转,但树的高度减小了。
### 3.左右平衡处理
类似于删除,我们也将删除分为左右两种平衡处理。由于删除后的平衡处理会导致树的高度可能发生变化,前面提到过,所以在删除的左右平衡处理中会多一个对shorter(标记当前树是否变矮)的修改。
public static boolean shorter;// 记录树是否变矮的标志,若shorter=true要进行相应的调整
public void DeLeftBalance(BSTNode T) {
// 删除结点导致以T的左孩子为根节点的树高度改变,T结点失衡,做左平衡处理
BSTNode p = T.getRchild();
switch (p.getBf()) {
case LH:
// 先改结点的BF值
BSTNode q = p.getLchild();
switch (q.getBf()) {
case LH:
T.setBf(EH);
p.setBf(RH);
break;
case EH:
T.setBf(EH);
p.setBf(EH);
break;
case RH:
T.setBf(LH);
p.setBf(EH);
break;
}
q.setBf(EH);
// 再先右-左旋
R_Rotate§;
L_Rotate(T);
shorter = true;
break;
case EH:// 左旋
p.setBf(LH);
T.setBf(RH);
L_Rotate(T);
shorter = false;
break;
case RH:// 左旋
p.setBf(EH);
T.setBf(EH);
L_Rotate(T);
shorter = true;
break;
}
}
public void DeRightBalance(BSTNode T) {
// 删除结点导致以T的右侧孩子为根节点的树高度改变,T结点失衡,做右平衡处理
BSTNode p = T.getLchild();
switch (p.getBf()) {
case LH:
T.setBf(EH);
p.setBf(EH);
R_Rotate(T);
shorter = true;
break;
case EH:
T.setBf(LH);
p.setBf(RH);
R_Rotate(T);
shorter = false;
break;
case RH:
BSTNode q = p.getRchild();
switch (q.getBf()) {
case LH:
T.setBf(RH);
p.setBf(EH);
break;
case EH:
T.setBf(EH);
p.setBf(EH);
break;
case RH:
T.setBf(EH);
p.setBf(LH);
break;
}
q.setBf(EH);
L_Rotate§;
R_Rotate(T);
shorter = true;
break;
}
}
### 4.删除的实现
删除的实现和插入的实现很相似,删除也是通过地归来实现的,通过递归比较容易实现从下到上平衡的调整。稍微复杂一点的地方就是找到删除的结点但该结点左右子树都不空,这时候需要用我之前说的交换思想继续调用deleteAVL()方法,而且最多只用交换两次。如果还不明白的话可以参照下面给出的代码手动模拟一下交换的过程应该就会明白了。
public boolean deleteAVL(BSTNode T, int x) {// 删除成功返回true,返回失败返回false
if (T == null) {
System.out.println(“树中不存在该结点,删除失败!”);
shorter = false;
return false;
}
if (T.getData() == x) {
if (T.getLchild() != null && T.getRchild() != null) {// 需要删除的结点左右子树都不为空
BSTNode p = T.getLchild();
while (p.getRchild() != null) {
p = p.getRchild();
}
int value = p.getData();
deleteAVL(T, value);// ★★
T.setData(value);
if (shorter) {// 调整(结点的左子树高度减少1)
switch (T.getBf()) {
case LH://
T.setBf(EH);
shorter = true;
break;
case EH:
T.setBf(RH);
shorter = false;
break;
case RH:
DeLeftBalance(T);// shorter在DeLeftBalance(T)函数里面改变
break;
}
}
} else if (T.getBf() == LH) {// T只有一个左孩子结点
int value = T.getLchild().getData();
deleteAVL(T, value);// ★★
T.setData(value);
// 调整
T.setBf(EH);
shorter = true;
} else if (T.getBf() == RH) {// 只有右孩子结点
int value = T.getRchild().getData();
deleteAVL(T, value);// ★★
T.setData(value);
// 调整
T.setBf(EH);
shorter = true;
} else {// 左右子树都为空
if (T.getParent() == null) {// 删除的是根节点且只树有一个结点的情况
root = null;
} else {
delectNode(T);
shorter = true;// 删除结点,树变矮了
// return true;可以省略,函数的最后一行会返回true
}
}
} else if (x < T.getData()) {
if (!deleteAVL(T.getLchild(), x)) {// ★★
return false;
}
if (shorter) {// 调整(左子树中删除)
switch (T.getBf()) {
case LH:
T.setBf(EH);
shorter = true;
break;
case EH:
T.setBf(RH);
shorter = false;
break;
case RH:
DeLeftBalance(T);
break;
}
}
} else {
if (!deleteAVL(T.getRchild(), x)) {// ★★
return false;
}
if (shorter) {// 调整(右子树中删除)
switch (T.getBf()) {
case LH:
DeRightBalance(T);
break;
case EH:
T.setBf(LH);
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
Bf()) {
case LH:
DeRightBalance(T);
break;
case EH:
T.setBf(LH);
[外链图片转存中…(img-z3ga7jzn-1715492309975)]
[外链图片转存中…(img-n2Lzc7rO-1715492309975)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!