一、AVL树的定义
在计算机科学中,AVL树是最先发明的自平衡二叉查找树。在AVL树中任何节点的两个子树的高度最大差别为1,所以它也被称为高度平衡树。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。AVL树得名于它的发明者G. M. Adelson-Velsky和E. M. Landis,他们在1962年的论文《An algorithm for the organization of information》中发表了它。
——百度百科
二、AVL树添加、删除元素
说明:
AVLTress: 外部类
root: AVL树的根节点
nodeStack: 存放某次删除元素时遍历过的节点栈
Node: 内部类,节点类型
this:当前节点
left:当前节点的左子节点
right:当前节点的右子节点
value: 当前节点存放的值
height(): 当前节点高度
rightHeight:右子树高度
leftHeight():左子树高度
leftRoate():令当前节点左旋转
rightRotate():令当前节点右旋转
balance(): 调整以当前节点为根节点的子树平衡
1.添加
往AVL树 this 添加新节点 node 的思想是使用递归 :
外部类函数 add(E value) , 由根节点调用 root.add( new Node( value ) )
内部类函数 add(Node node),由当前节点调用
步骤:
- node.value <= this.value ,将其添加到当前树的左子树,
this.left !=null 则 this.left.add( node )
this.left ==null , this.left = node- nodevalue > this.value , 将其添加到当前树的右子树
this.right != null 则 this.right.add( node )
this.right == null , this.right = node
在每次寻找适当的插入位置时,当前节点 this 遍历过的节点为所有可能发生失衡的节点,因此需要在插入节点完成后对当前节点进行平衡调整:this.balance()。
添加元素 代码:
/**
* Desc: 添加新元素 value
*
* @author pikachu
*/
public void add(E value) {
if (root == null) {
root = new Node(value);
} else {
root.add(new Node(value));
}
}
/**
* Desc: 将 node加入到当前节点 this
*
* @param node 待加入节点
* @author pikachu
*/
private void add(Node node) {
if (node.value.compareTo(this.value) <= 0) {
if (this.left != null) {
this.left.add(node);
} else {
this.left = node;
}
} else {
if (this.right != null) {
this.right.add(node);
} else {
this.right = node;
}
}
balance();
}
2.旋转
往AVL树中添加元素时可能会破环树的平衡,处理形况分为4种:LL、RR、LR、RL。
对树进行旋转时总是以高度较大的子树为圆心旋转,从而降低其高度,
左旋转:逆时针旋转
右旋转:顺时针旋转
LL
LL型:破环平衡的节点在左子树的左子节点,leftHeight>rightHeight && left.leftHeight >= left.rightHeight,4(this)绕2(left)右转(顺时针)
RR
RR型:破环平衡的节点在右子树的右子节点,rightHeight>leftHeight && right.rightHeight >= right.leftHeight,6(this)绕8(right)左转(逆时针)
LR
LR型:破环平衡的节点在左子树的右子节点,leftHeight > rightHeight &&
left.rightHeight > left.leftHeight。
- 2(left)绕3(left.right)左转(逆时针)
- 5(this)绕3(left)右旋转(顺时针)
RL
RL型:破环平衡的节点在右子树的左子节点, rightHeight > leftHeight &&
right.leftHeight > right.rightHeight。
- 5(right)绕4(right.left)右转(顺时针)
- 2(this)绕4(right)左旋转(逆时针)
调节平衡代码
/**
* Desc:调节当前节点的子树平衡
*/
private void balance() {
// 平衡调整
if (rightHeight() - leftHeight() > 1) {
if (right.rightHeight() >= right.leftHeight()) {
//RR型
leftRotate();
} else {
//RL型
right.rightRotate();
leftRotate();
}
return;
}
if (leftHeight() - rightHeight() > 1) {
if (left.leftHeight() >= left.rightHeight()) {
//LL型
rightRotate();
} else {
//LR型
left.leftRotate();
rightRotate();
}
}
}
3.删除
remove( Node parent, Node delNode ) : 移除preNode的子树中的node节点(如果存在), 当待删除的节点有左右子树时,将右子树的最小节点作为 parent的新子节点, 删除后将遍历路径上的节点nodesStack平衡。
说明:
this 当前节点
parent 当前节点的父节点
delNode 待删除节点
rightMinNode 扫描 this 右子树最小节点(this.right != null 时实际删除的节点)
preNode rightMinNode的父节点,初始为this
寻找待删除节点时的路径可能不只是每次遍历的当前节点 this ,还有 preNode 遍历过的节点。如以下情况:
此时 preNNode 遍历过的节点 17 失衡,因此每次当 preNode 发生移动时也需要将 preNode 压入 nodeStack。
步骤:
- delNode < this,往左子树查找删除,若左子树为空则结束
- delNode > this,往右子树查找删除,若右子树为空则结束
- delNode = this 找到待删除的节点 this
3.1 this.right不为空,在 this.right的子树中寻找最小节点 rightMinNode,作为新子树的根节点
3.1.1preNode和rightMinNode发生移动,待删除节点的右子节点有左子树, 并在该树(根节点 rightMinNode,left = null, right 不一定为 null)中找到最小值。
3.1.2 preNode和rightMinNode没有移动,待删除节点的右子节点无左子树,this.right.left = null,
右子树的最小节点就是this.right,令this拼接上this.right.right。
3.1.3 将 this.value 替换为 rightMinNode.value
3.2 this.right为空
3.2.1 this =parent.right),parent.right = this.left;
this = parent.left, parent.left = this.left;
3.2.2 this = parent ,删除树中根节点,替换根节点 root = root.left;
删除元素时也可能破环树的平衡,需要记录删除时遍历的节点 nodeStack,然后将其逆序取每个遍历过的节点 node 进行平衡化 : node.balance();
删除元素代码
/**
* Desc:
*
* @param value 待移除的元素
* @author pikachu
*/
public void remove(E value) {
if (root == null) {
System.out.println("Tree is empty");
} else {
root.remove(root, new Node(value));
// 本次删除结束,将nodes清空
nodeStack.clear();
}
}
/**
* Desc:移除preNode的子树中的node节点(如果存在),
* 当待删除的节点有左右子树时,将右子树的最小节点作为 parent的新子节点,
* 删除后将遍历路径上的节点nodesStack平衡
*
* @param parent 当前节点的父节点
* @param delNode 待删除节点
* @author pikachu
*/
private void remove(Node parent, Node delNode) {
nodeStack.push(this);
// 1.delNode < this,往左子树查找删除;若左子树为空则结束
if (delNode.value.compareTo(this.value) < 0) {
if (this.left != null) {
this.left.remove(this, delNode);
}
}
// 2.delNode > this,往右子树查找删除;若右子树为空则结束
else if (delNode.value.compareTo(this.value) > 0) {
if (this.right != null) {
this.right.remove(this, delNode);
}
}
// 3.delNode = this 找到待删除的节点 this
else if (delNode.value.compareTo(this.value) == 0) {
Node preNode = this;
// 3.1 this.right不为空,在 this.right的子树中寻找最小节点 rightMinNode,作为新子树的根节点
if (this.right != null) {