1、概念:
平衡二叉树(Balanced Binary Tree)具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
1.1 平衡因子:
左子树的高度减去右子树的高度,及B = B左 - B右。由平衡二叉树的定义可知,平衡因子的取值只可能为0,1,-1。0:左右子树等高。1:左子树比较高。-1:右子树比较高
1.2 最小不平衡子树
距离插入结点最近的,且平衡因子绝对值大于1的结点为根 的子树,我们称为最小不平衡子树
1.3 AVL调整----旋转
1.3.1左旋:
//1、获取根节点的左孩子,将左孩子升级为当前最小不平衡书的根节点(后面仍以左孩子称呼)
//2、将左孩子的右节点绑定到原根节点的左子树上
//3、将原根节点绑定到左孩子的右子树上
1.3.2右旋
//1、获取当前最小不平衡树根节点的右孩子,将其升级为当前最小不平衡书的根节点(后面仍以右孩子称呼)
//2、如果右孩子有左孩子,放到原根节点的右子树上
//3、将原根节点变为右孩子的左孩子
1.3.3 旋转使用条件:
右旋:最小不平衡子树的BF>=2 且 它的左子树BF符号>0
左旋:最小不平衡子树的BF<=-2 且 它的右子树BF符号<0
左右旋:最小不平衡子树的BF>=2 且 它的左子树BF符号<0。需要先对左子树进行左旋,然后对最小不平衡子树右旋
右左旋:最小不平衡子树的BF<=-2 且 它的右子树BF符号>0,需要先对右子树进行右旋,然后对最小不平衡子树左旋
LL,LR,RR,RL
2、代码展示
注意两点:
1、求节点的高度,递归:
private int getTreeDepth(TreeNode node) {
if (node == null) {
return 0;
}
return 1 + Math.max(getTreeDepth(node.left), getTreeDepth(node.right));
}
2、添加时旋转时机的把握。
3、删除的逻辑(这块好像还是不太对,从删除点开始重新整理书不太对,先这样处理,有机会回来改正,或者有大佬看到了给指点一下)
package com.suirui.binarytree.AVL;
import javafx.scene.Parent;
import javax.xml.soap.Node;
import java.util.ArrayList;
import java.util.LinkedList;
/**
* Created by zongx on 2020/2/25.
*/
public class AVLTree {
private TreeNode root = null;
private final int LEFT = 1;
private final int RIGHT = -1;
private final int MAX_LEFT = 2;
private final int MAX_RIGHT = -2;
//右转---传入最小不平衡子树的根节点
public TreeNode rightRotation(TreeNode node) {
if (node == null) {
return null;
}
//1、获取根节点的左孩子,将左孩子升级为当前最小不平衡书的根节点(后面仍以左孩子称呼)
TreeNode leftChild = node.left;
leftChild.parent = node.parent;
if (node.parent == null) {
//如果为null的话,代表当前node是整个AVL树的根节点,所以AVL根节点替换为leftChild
this.root = leftChild;
} else if (node.parent.left == node) {
//如果node在其父节点的左子树上,将其左孩子绑定到其父节点的左子树上
node.parent.left = leftChild;
} else if (node.parent.right == node) {
//如果node在其父节点的右子树上,将其左孩子绑定到其父节点的右子树上
node.parent.right = leftChild;
}
//2、将左孩子的右节点绑定到原根节点的左子树上
node.left = leftChild.right;
if (leftChild.right != null) {
leftChild.right.parent = node;
}
//3、将原根节点绑定到左孩子的右子树上
leftChild.right = node;
node.parent = leftChild;
//4、返回最新根节点
return leftChild;
}
public TreeNode leftRotation(TreeNode node) {
if (node == null) {
return null;
}
//1、获取当前最小不平衡树根节点的右孩子,将其升级为当前最小不平衡书的根节点(后面仍以右孩子称呼)
TreeNode rightChild = node.right;
rightChild.parent = node.parent;
if (node.parent == null) {
this.root = rightChild;
} else if (node.parent.left == node) {
node.parent.left = rightChild;
} else if (node.parent.right == node) {
node.parent.right = rightChild;
}
//2、如果右孩子有左孩子,放到原根节点的右子树上
node.right = rightChild.left;
if (rightChild.left != null) {
rightChild.left.parent = node;
}
//3、将原根节点变为右孩子的左孩子
rightChild.left = node;
node.parent = rightChild;
return rightChild;
}
/**
* @return void
* @Description: 从根节点插入数据
* @Author: zongx
* @Date: 2020/2/26
* @Param: root
* @Param: data
*/
public boolean add(int value) {
//1、如果没有根节点,则将当前节点数据作为根节点
if (this.root == null) {
this.root = new TreeNode(value);
return true;
}
//2、判断value节点的位置
//从根节点开始比较
TreeNode current = root;
while (true) {
//要插入节点大于或等于当前节点,则判断右子树
if (value >= current.data) {
if (current.right == null) {
current.right = new TreeNode(value, current);
break;
}
current = current.right;
}else {
//要插入节点小于当前节点,则判断左子树
if (current.left == null) {
current.left = new TreeNode(value, current);
break;
}
current = current.left;
}
}
//此时current就是新加入节点的父节点
//3、对树进行旋转使其满足AVL条件
rebuild(current);
return true;
}
/**
* @return void
* @Description: 对树进行再平衡
* @Author: zongx
* @Date: 2020/2/26
* @Param: node
*/
private void rebuild(TreeNode node) {
//从给定的node节点开始,一直到根节点判断平衡因子。
while (node != null) {
int bf = calcNodeBalanceValue(node);
if (bf == MAX_LEFT) {
//计算出的平衡因子是+2,代表左子树高
fixInsertion(node, LEFT);
} else if (bf == MAX_RIGHT) {
//计算出的平衡因子是-2,代表左子树高
fixInsertion(node, RIGHT);
}
node = node.parent;
}
}
/**
* @return int
* @Description: 计算平衡因子
* @Author: zongx
* @Date: 2020/2/26
* @Param: node
*/
private int calcNodeBalanceValue(TreeNode node) {
if (node == null) {
return 0;
}
//返回左子树高度减去右子树高度
return getTreeDepth(node.left) - getTreeDepth(node.right);
}
/**
* @return int
* @Description: 获取根节点node的树的高度, 递归方式,
* @Author: zongx
* @Date: 2020/2/26
* @Param: node
*/
private int getTreeDepth(TreeNode node) {
if (node == null) {
return 0;
}
return 1 + Math.max(getTreeDepth(node.left), getTreeDepth(node.right));
}
/**
* @return void
* @Description: 根据平衡因子进行旋转操作
* @Author: zongx
* @Date: 2020/2/26
* @Param: node 此处的node是最小不平衡树的根节点
* @Param: left
*/
private void fixInsertion(TreeNode node, int type) {
//右旋:最小不平衡子树的BF>=2 且 它的左子树BF符号>0
//左旋:最小不平衡子树的BF<=-2 且 它的右子树BF符号<0
//左右旋:最小不平衡子树的BF>=2 且 它的左子树BF符号<0。需要先对左子树进行左旋,然后对最小不平衡子树右旋
//右左旋:最小不平衡子树的BF<=-2 且 它的右子树BF符号>0,需要先对右子树进行右旋,然后对最小不平衡子树左旋
if (type > 0) {
//因为是左树比较高,所以判断node节点的左孩子的情况
TreeNode leftChild = node.left;
int childBf = calcNodeBalanceValue(leftChild);
if (childBf > 0) {
//左孩子平衡因子>0,根节点平衡因子>0----右旋
rightRotation(node);
} else if (childBf < 0) {
//左孩子平衡因子<0,根节点平衡因子>0----左右旋
leftRotation(leftChild);
rightRotation(node);
}
return;
}
if (type == this.RIGHT) {
//因为是右树比较高,所以判断node节点的右孩子的情况
TreeNode rightChild = node.right;
int childBf = calcNodeBalanceValue(rightChild);
if (childBf < 0) {
//右孩子平衡因子<0,根节点平衡因子<0----左旋
leftRotation(node);
} else if (childBf > 0) {
//右孩子平衡因子>0,根节点平衡因子<0----右左旋
rightRotation(rightChild);
leftRotation(node);
}
return;
}
}
public void midOrder() {
if(this.root == null) {
return;
}
System.out.println("--------中序遍历--------");
LinkedList<TreeNode> stack = new LinkedList<>();
TreeNode current = this.root;
while(!stack.isEmpty() || current != null) {
if (current != null) {
stack.push(current);
current = current.left;
}else {
current = stack.pop();
System.out.print(current.data+",");
current = current.right;
}
}
System.out.println();
}
public void levelOrder() {
if(this.root == null) {
return;
}
System.out.println("--------层次遍历--------");
LinkedList<TreeNode> queue = new LinkedList<>();
TreeNode current = this.root;
while (current != null || !queue.isEmpty()) {
System.out.println("当前节点值:" + current.data + ", BF:" + calcNodeBalanceValue(current));
if (current.left != null) {
queue.add(current.left);
}
if (current.right != null) {
queue.add(current.right);
}
current = queue.poll();
}
System.out.println();
}
public TreeNode getNode(int value) {
TreeNode current = this.root;
while (current != null){
if (current.data == value) {
return current;
}else if (current.data > value) {
current = current.left;
}else {
current = current.right;
}
}
return null;
}
/**
* @Description: 基本沿用二叉搜索树的逻辑,只是此时treenode结构发生变化,要考虑parent属性。还要考虑
* @Author: zongx
* @Date: 2020/2/26
* @Param: value
* @return void
*/
public void delete(int value) {
TreeNode delNode = getNode(value);
//未找到。直接返回
if (delNode == null) {
System.out.println("没有要删除的元素");
return;
}
TreeNode parent = delNode.parent;
//分三种情况进行讨论。
//1、删除的节点没有子树,将其父节点的子节点置为null
if (delNode.left == null && delNode.right == null) {
if (delNode == this.root) {
this.root = null;
} else if (delNode.parent.left == delNode) {
delNode.parent.left = null;
} else if (delNode.parent.right == delNode) {
delNode.parent.right = null;
}
rebuild(parent);
return;
}
//2、删除的节点只有一颗子树,将其父节点的子节点置为其子树
//2.1 左子树不为空
if (delNode.left != null && delNode.right == null) {
if (delNode == this.root) {
//因为是双向关连,所以新节点与父节点之间要相互关联
delNode.left.parent = delNode.parent;
this.root = delNode.left;
} else if (delNode.parent.left == delNode) {
//因为是双向关连,所以新节点与父节点之间要相互关联
delNode.left.parent = delNode.parent;
delNode.parent.left = delNode.left;
} else if (delNode.parent.right == delNode) {
//因为是双向关连,所以新节点与父节点之间要相互关联
delNode.left.parent = delNode.parent;
delNode.parent.right = delNode.left;
}
rebuild(parent);
return;
}
//2.2 右子树不为空
if (delNode.right != null && delNode.left == null) {
if (delNode == this.root) {
//因为是双向关连,所以新节点与父节点之间要相互关联
delNode.right.parent = delNode.parent;
this.root = delNode.right;
} else if (delNode.parent.left == delNode) {
//因为是双向关连,所以新节点与父节点之间要相互关联
delNode.right.parent = delNode.parent;
delNode.parent.left = delNode.right;
} else if (delNode.parent.right == delNode) {
//因为是双向关连,所以新节点与父节点之间要相互关联
delNode.right.parent = delNode.parent;
delNode.parent.right = delNode.right;
}
rebuild(parent);
return;
}
//3 左右子树均不为空,此时的操作需要找寻后继节点,并修改结构
TreeNode successor = getSuccessor(delNode);
if (delNode == this.root) {
// successor.parent = delNode.parent;
this.root = successor;
}else if (delNode.parent.left == delNode) {
//因为是双向关连,所以新节点与父节点之间要相互关联
successor.parent = delNode.parent;
delNode.parent.left = successor;
} else if (delNode.parent.right == delNode) {
//因为是双向关连,所以新节点与父节点之间要相互关联
successor.parent = delNode.parent;
delNode.parent.right = successor;
}
rebuild(parent);
}
/**
* @Description: 获取后继节点(具体看二叉搜索树该部分)
* @Author: zongx
* @Date: 2020/2/26
* @Param: current
* @return com.suirui.binarytree.AVL.TreeNode
*/
private TreeNode getSuccessor(TreeNode delNode) {
//此处的前提是,current肯定具有左右孩子
//后继节点是中序遍历的下一个节点,所以肯定是右孩子左子树的最后一个
TreeNode current = delNode.right;
while (current.left != null) {
current = current.left;
}
//修改结构,如果后继节点不是直接是右孩子,则需要改造右子树
if (current != delNode.right) {
//1、如果current有右子树,将current当前节点的右子树给当前节点的父节点
if (current.right != null) {
current.right.parent = current.parent;
}
current.parent.left = current.right;//这块直接使用left是因为,当前节点是通过left找到的,所以在left上
//2、将要删除节点delNode的右子树,给当前节点
delNode.right.parent = current;
current.right = delNode.right;
}
//为后继节点赋值删除节点的左子树(后继节点应该没有左子树)
delNode.left.parent = current;
current.left = delNode.left;
return current;
}
public static void main(String[] args) {
AVLTree avlTree = new AVLTree();
avlTree.add(10);
avlTree.add(7);
avlTree.add(15);
avlTree.add(3);
avlTree.add(8);
avlTree.add(20);
avlTree.midOrder();
avlTree.levelOrder();
avlTree.delete(20);
avlTree.delete(15);
avlTree.midOrder();
avlTree.levelOrder();
}
}