平衡二叉树
特点
- 首先它满足二叉查找树的所有特点
- 任何节点的两个子树的高度最大差为1
新增和删除节点可能导致avl树失去平衡,可以通过旋转重新达到平衡
缺点
- 要求严格,几乎每一次操作都会破坏平衡,需要进行旋转调整。
右旋、左旋、双旋(以右旋为例、左旋同理)
当左子树的高度高于右子树的高度时,我们需要进行右旋操作
当右子树的高度高于左子树的高度时,我们需要进行左旋操作
右旋为例
分两种情况:
一、插入的节点是左子节点
- 此时将目标节点向右旋转即可,即左子节点【8】提升为父节点,目标节点【10】成为原来左子节点【8】的右子节点
二、插入的节点是右子节点
-
此时我们直接右旋转会发现仍不平衡
-
我们可以先将节点【8】左旋,在将节点【10】右旋
-
第一步、节点【9】替换节点【8】的位置,节点【8】成为【9】的左子节点
-
第二步、与普通右旋相同
JAVA代码实现
代码大部分内容与二叉查找树相同,AVL树本质上就是BST树,这里添加了一些处理平衡的方法
- 获取节点左子节点和右子节点的深度
- 判断节点是否平衡 (深度差<2)
- 按照节点左高还是右高进行旋转
- 注意根节点的处理(旋转之后成为根节点的节点要清除原来的父节点信息)
节点类
public class Node {
private Integer value;
private Node parentNode;
private Node leftNode;
private Node rightNode;
public Node(){
}
/**
* 当节点上升为根节点时,提供可以清除父节点的方法
*/
public void clearParent(){
this.parentNode =null;
}
/**
* 自动设置父节点
* @param node
*/
private void setParentNode(Node node){
this.parentNode = node;
}
public Node getParentNode(){
return this.parentNode;
}
public Integer getValue(){
return this.value;
}
public void setValue(Integer value){
this.value = value;
}
public Node getLeftNode(){
return this.leftNode;
}
public void setLeftNode(Node node){
if(null != node ) node.setParentNode(this);
this.leftNode = node;
}
public Node getRightNode(){
return this.rightNode;
}
public void setRightNode(Node node){
if(null != node ) node.setParentNode(this);
this.rightNode = node;
}
/**
* 前序遍历
*/
public void DLR(){
if(value!=null) {
System.out.print(String.format("【%d】", value));
if (this.leftNode != null) {
this.leftNode.DLR();
}
if (this.rightNode != null) {
this.rightNode.DLR();
}
}
}
/**
* 中序遍历
*/
public void LDR(){
if(value!=null) {
if (this.leftNode != null) {
this.leftNode.LDR();
}
System.out.print(String.format("【%d】", value));
if (this.rightNode != null) {
this.rightNode.LDR();
}
}
}
/**
* 后续遍历
*/
public void LRD() {
if (value != null) {
if (this.leftNode != null) {
this.leftNode.LRD();
}
if (this.rightNode != null) {
this.rightNode.LRD();
}
System.out.print(String.format("【%d】", value));
}
}
}
平衡二叉树操作类
public class AvlTree {
private Node root;
public Node getTree(){
return this.root;
}
/**
* 插入元素
* @param i
*/
public void insert(int i){
Node newNode = new Node();
newNode.setValue(i);
if(root==null){
//没有根结点时 将当前节点作为根节点
root=newNode;
}else{
Node currentNode = root;
Node parentNode;
while (null != currentNode) {
// 当前节点视为父节点
parentNode = currentNode;
if (i > currentNode.getValue()) {
// 大于父节点的值 且父节点没有右子节点 则作为右节点插入
currentNode = currentNode.getRightNode();
if (null == currentNode) {
parentNode.setRightNode(newNode);
}
} else if(i < currentNode.getValue()) {
// 小于父节点的值 且父节点没有左子节点 则作为左节点插入
currentNode = currentNode.getLeftNode();
if (null == currentNode) {
parentNode.setLeftNode(newNode);
}
}
}
//插入之后 平衡树结构
if (newNode != null) {
balance(newNode);
}
}
}
/**
* 因为原来的树是平衡的 所以我们从插入的节点向上判断
* @param node
*/
private void balance(Node node) {
Node target = node;
//从目标节点向上遍历左右节点的最大树高差
while (target != null) {
Node l = target.getLeftNode();
Node r = target.getRightNode();
int deep = getDeep(l) - getDeep(r);
//左高于右 右旋
if (deep == 2) {
rightRotate(node, target);
break;
}
//右高于左 左旋
if (deep == -2) {
leftRotate(node, target);
break;
}
target = target.getParentNode();
}
}
/**
* 获取节点的深度
* @param node
* @return
*/
private int getDeep(Node node) {
if (node == null) {
return 0;
}
//自身节点 高为1
if (node.getLeftNode() == null && node.getRightNode() == null) {
return 1;
}
return 1 + Math.max(getDeep(node.getLeftNode()), getDeep(node.getRightNode()));
}
/**
* 左树高于右树 右旋
* 插入的节点是 右子节点 需要先架构插入节点的父节点左旋
* @param node
* @param target
*/
private void rightRotate(Node node, Node target) {
if (node == node.getParentNode().getRightNode()) {
leftRound(node.getParentNode());
}
rightRound(target);
}
/**
* 右旋操作
* @param node
*/
private void rightRound(Node node) {
// 目标节点的左子节点
Node left = node.getLeftNode();
// 目标节点的父节点
Node pre = node.getParentNode();
if (pre == null) {
// 若父节点为空,即目标节点原本是根节点,则目标节点左孩子晋升为根节点
//清除根节点原来的父节点信息
left.clearParent();
root = left;
} else {
//left 作为 pre 的子节点 即left替换原来node的位置
updateNode(node,left);
}
//left的右子节点 作为 node的左子节点
node.setLeftNode(left.getRightNode());
//node 作为 left的右子节点
left.setRightNode(node);
}
/**
* 右树高于左树 左旋
* 插入的节点是 左子节点 需要先架构插入节点的父节点右旋
* @param node
* @param target
*/
private void leftRotate(Node node, Node target) {
if (node == node.getParentNode().getLeftNode()) {
rightRound(node.getParentNode());
}
leftRound(target);
}
/**
* 左旋操作
* @param node
*/
private void leftRound(Node node) {
// 目标节点的右子节点
Node right = node.getRightNode();
// 目标节点的父节点
Node pre = node.getParentNode();
if (pre == null) {
// 若父节点为空,即目标节点原本是根节点,则目标节点左孩子晋升为根节点
//清除根节点原来的父节点信息
right.clearParent();
root = right;
} else {
//right 作为 pre 的子节点 即right替换原来node的位置
updateNode(node,right);
}
//right的左子节点 作为 node的右子节点
node.setRightNode(right.getLeftNode());
//node 作为 right的左子节点
right.setLeftNode(node);
}
/**
* 查找元素节点
* @param key
* @return
*/
public Node find(int key) {
Node currentNode = root;
if (null != currentNode) {
// 判断是否是当前节点
while (key != currentNode.getValue()) {
//值大于当前节点 向右遍历
if (key > currentNode.getValue()) {
currentNode = currentNode.getRightNode();
} else {
//值小于当前节点 向左遍历
currentNode = currentNode.getLeftNode();
}
if (null == currentNode) {
return null;
}
}
}
return currentNode;
}
/**
* 查找 node 的后继节点
* @param node
* @return
*/
public Node successor(Node node) {
//后继节点 即 右节点的最小值
Node minNode = null;
if(node.getRightNode()!=null){
minNode=node.getRightNode();
while(minNode!=null){
Node currentChild = minNode.getLeftNode();
if(currentChild!=null){
minNode = minNode.getLeftNode();
}else{
break;
}
}
}
return minNode;
}
/**
* 用新的节点替换当前节点
* @param oldNode
* @param newNode
*/
private void updateNode(Node oldNode,Node newNode){
if (oldNode.getParentNode().getLeftNode() == oldNode){
oldNode.getParentNode().setLeftNode(newNode);
}else if (oldNode.getParentNode().getRightNode() == oldNode){
oldNode.getParentNode().setRightNode(newNode);
}
}
/**
* 删除节点 分三种情况
* 叶子节点 直接删除
* 有一个子节点 将子节点的父节点设置为被删除节点的父节点
* 左右节点都存在时 可以先找到需要删除的节点的后继节点或前驱节点替换当前节点
* @param key
*/
public void delete(int key) {
Node deleteNode = find(key);
if(deleteNode!=null){
Node parent = deleteNode.getParentNode();
if(deleteNode.getLeftNode()!=null && deleteNode.getRightNode()!=null){
//两个子节点,查找后继节点替换当前节点
Node backContinueNode = successor(deleteNode);
//将后继节点 替换为 NULL
this.updateNode(backContinueNode,null);
//将待删除节点的子节点内容赋值给 后继节点
backContinueNode.setLeftNode(deleteNode.getLeftNode());
//后继节点有右子节点
Node rightNode = backContinueNode.getRightNode();
if(rightNode!=null){
//获得最后一个右子节点 赋值
while(rightNode!=null){
if(rightNode.getRightNode()!=null){
rightNode=rightNode.getRightNode();
}else{
//最后一个右子节点
rightNode.setRightNode(deleteNode.getRightNode());
break;
}
}
}else{
backContinueNode.setRightNode(deleteNode.getRightNode());
}
//将待删除节点 替换为 后继节点
//需要删除的节点没有被引用的地方,会自动回收
this.updateNode(deleteNode,backContinueNode);
}else if(deleteNode.getLeftNode()!=null){
//只有左子节点 将左子节点和父节点直接相连
this.updateNode(deleteNode,deleteNode.getLeftNode());
}else if(deleteNode.getRightNode()!=null){
//只有右子节点 将右子节点和父节点直接相连
this.updateNode(deleteNode,deleteNode.getRightNode());
}else{
//没有子节点
this.updateNode(deleteNode,null);
}
//平衡操作
if(parent!=null){
balance(parent);
}else{
balance(root);
}
}else{
throw new RuntimeException("没有该节点");
}
}
}