6.2 AVL树
作者:互联网
平衡二叉树,比较简单,二叉树+平衡。
二叉树的意思,就是树只有两个节点,平衡的意思是左边和右边的层数要平衡。也就是层数相差不能超过一。而且每个子树也要是平衡二叉树。
平衡二叉树还有个特性是排序。排序所谓排序,就是左边要比自己小,并且右边比自己大。
可以先实现一个排序树。
排序树的实现很简单。作为树,核心是两个类。
要做这么一个复杂的树,必须要用到面向对象的思想。
首先是树,子类是二叉树,再下面是排序树,再下面是平衡树。这样就做成了一个平衡二叉树。
一个是Tree,一个是Node.
Tree的代码可以是这样。
public interface Tree<T> {
Node<T> getRoot();
void add(T t);
void delete(T t);
Node<T> findNode(T t);
}
那么Node的代码,可以是这样:
public interface Node<T> {
T getValue();
Node<T> getParent();
List<? extends Node<T>> getChildren();
}
这样就定义好了两个主要的接口。
类继承关系,可以是
Tree -> BinaryTree -> OrderBinaryTree -> AVLTree
总共四层。树,然后是二叉树,再是排序二叉树,最后是平衡排序二叉树。
平衡二叉树的旋转
二叉树的核心就是添加完成之后,判断一下树是否平衡,如果不平衡就进行旋转调整。
旋转类型分为四种。
LL RR LR RL。
LL旋转又叫右旋。是在左子树深度远大于右子树的情况下进行的旋转。
LL的意思是左子树加了个左孩子的意思。
同理,RR是右子树多了个右孩子。
LR是左子树多了个右孩子。
RL是右子树多了个左孩子。
LL LR都属于L开头,意思是左边深度大于右边。
旋转时是值得拷贝,而不是把根换掉。而且旋转时要寻找最小子树。
以下是四大旋转的代码:
/**
* 3 2
* 2 -> 1 3
* 1
* @return
*/
private void ll() {
final AVLNode<T> newRight = new AVLNode<>(this.value);
newRight.setRight(this.getRight());
newRight.setLeft(this.getLeft().getRight());
this.setRight(newRight);
this.value = this.getLeft().value;
this.getLeft().parent = null;
this.setLeft(this.getLeft().getLeft());
}
/**
* 1 2
* 2 -> 1 3
* 3
* @return
*/
private void rr() {
final AVLNode<T> newLeft = new AVLNode<>(this.value);
newLeft.setLeft(this.getLeft());
newLeft.setRight(this.getRight().getLeft());
this.setLeft(newLeft);
this.value = this.getRight().value;
this.getRight().parent = null;
this.setRight(this.getRight().getRight());
}
/**
* 7 3
* 2 -> 2 7
* 3
* @return
*/
private void lr() {
this.getLeft().rr();
this.ll();
}
/**
* 5 6
* 7 -> 5 7
* 6
* @return
*/
private void rl() {
this.getRight().ll();
this.rr();
}
```java
寻找最小不平衡子树的代码:
```java
private void fixToBalance(AVLNode<T> avlNode) {
// 往上找直到子树不平衡
for(AVLNode<T> pointer = avlNode;pointer != null; pointer = (AVLNode<T>) pointer.parent) {
if (pointer.getLeftHeight() - pointer.getRightHeight() > 1) {
// LL 或 LR
final AVLNode<T> left = pointer.getLeft();
if (left.getLeftHeight() > left.getRightHeight()) {
// LL
pointer.ll();
break;
}
if (left.getLeftHeight() < left.getRightHeight()) {
// LR
// 先子树RR,再整体LL
pointer.lr();
break;
}
}
if (pointer.getLeftHeight() - pointer.getRightHeight() < -1) {
// RR 或 RL
final AVLNode<T> right = pointer.getRight();
if (right.getLeftHeight() > right.getRightHeight()) {
// RL
pointer.rl();
break;
}
if (right.getLeftHeight() < right.getRightHeight()) {
// RR
pointer.rr();
break;
}
}
}
}
从代码可以看出是从新加的节点进行向上寻找,然后判断不平衡的类型。
AVL树的删除
AVL树的删除比添加节点要复杂。
第一步是删除吧。但是删除之后要符合二叉树的特征。
比如这棵树
要删除6
可以把7代替原来的位置,也可以用5代替原来的位置。
但是如果删除的是8呢?
只能将7放过来。代替原来的位置。
具体操作就是一个判断,如果左子树不为空,在左子树一直找右子节点,直到没有了为止。这样就找到了最大值。
否则,如果右子树不为空,找右子树的最小值。
在删除之后就进行旋转调整,同样是四种旋转。
代码如下:
// 排序二叉树的删除
final OrderBinaryNode<T> node = findNode(t);
if (node == null) {
return;
}
// 如果是root,并且为空
if (node == root && root.isEmpty()) {
root = null;
return;
}
// 如果是叶子
if (node.isEmpty()) {
final BinaryNode<T> parent = node.getParent();
parent.removeChild(node);
return;
}
// 否则就自己调整了
node.delete();
删除但是不调整的代码如下:
public void delete() {
// 排序树节点,删除自己之后还是个排序树。
// 这是自己不空的情况
// 分两种情况
if (getLeft() != null) {
// 左节点找出最大值
OrderBinaryNode<T> node = this.getLeft();
while (node.getRight() != null) {
node = node.getRight();
}
// 这是最大值,删除了自己
this.value = node.value;
node.parent.removeChild(node);
} else if (getRight() != null) {
// 右节点找出最小值
OrderBinaryNode<T> node = this.getRight();
while (node.getLeft() != null) {
node = node.getLeft();
}
this.value = node.value;
node.parent.removeChild(node);
}
}
而调节平衡调用添加时的调节方法就行了
标签:node,getLeft,getRight,value,AVL,6.2,pointer,二叉树 来源: https://blog.csdn.net/m0_66201040/article/details/122766072