什么是平衡二叉树
平衡树(Balance Tree,BT) 指的是,任意节点的子树的高度差都小于等于1
第二个树根节点左子树高度为3,右子树高度1,高度差为2,不平衡
二叉平衡搜索树(AVL树):自平衡的二叉排序树
上面的树,先变成二叉排序树
现在插入数据3,结点6左子树与右子树高度差大于2,不平衡,如果是AVL树会自动平衡(右旋)
为什么需要平衡
二叉排序树:任意结点,左节点不大于当前结点,右结点不小于当前结点的树
当数列为[1,2,3,4,5,6]等极端情况,二叉排序树就退化成了链表
其操作的时间复杂度将退化成线性的,即O(n),这不符合我们的需求
如何自平衡
平衡因子
平衡因子(bf):结点的左子树的深度减去右子树的深度
对应这些结点的平衡因子:
当存在平衡因子大于等于2,该树不平衡
四种不平衡情况
不平衡有四种情况:
- 插入左子树的左子树
插入结点1后,结点8,13的平衡因子都为2
- 插入左子树的右子树
- 插入右子树的左子树
- 插入右子树的右子树
上面四种情况本质上是两种情况,[1,4],[2,3]是对称的,但代码需要考虑到
自旋
既然AVL树是自平衡的二叉排序树,如何实现自平衡?
自旋平衡因子=>2或<=-2的结点
步骤:
- 插入接点90后,树不平衡,计算平衡因子
- 从插入结点90开始回溯,找到第一个失衡结点(66) - 如果回溯到根节点都没有失衡结点,该树是平衡树
- 平衡因子为-2,说明左子树比右子树高度小2,左旋该树
- 相应修改结点13、60、80间的线
选择哪个结点自旋? 从插入结点往上回溯,第一个失衡结点就是要旋转的结点
单旋转与双旋转
- 单旋转
如插入右子树的右子树这种情况(插入结点90),旋转一次就可以平衡,称为左旋
对应的左子树的左子树,旋转一次,称为右旋
- 双旋转
当不平衡状况出现2,3 - 左子树的右子树、右子树的左子树,单次旋转无法平衡
插入根结点为14的树的右子树的左子树(插入结点18),先右旋子树20,再左旋树14
相应的:根结点为14的树,插入左子树的右子树,先左旋子树9再右旋树14
删除结点
前面是插入结点后AVL树通过旋转自平衡,当删除结点时,也会引发不平衡
删除结点有3种情况:
- 删除叶子结点
删除结点8后,结点13的平衡因子为-2,不平衡
- 删除有一棵子树的结点
- 删除有左右子树的结点
这里设置的是删除有左右子树的结点,选择右子树中最小值顶替
构成AVL树后AVL已经是平衡树,删除结点导致树不平衡,我们只需要往父结点找失衡结点,找到后按照上面的旋转方法平衡即可
(删除双子树结点,应当从删除结点的位置开始找失衡结点)
Java实现
只需在二叉排序树的基础上完成左旋、右旋(双旋转都是实现两次左旋、右旋)
二叉排序树的内容,下面的代码基于我的二叉排序树
- 左旋
/**
* 左旋
*/
private void leftRotate(){
//创建一个新结点,为当前结点的值
Node newNode = new Node(value);
//把新的结点的左/右子树设置为当前结点的左子树
newNode.left = left;
newNode.right = right.left;
//把当前结点的值替换成右子树的值
value = right.value;
//把当前结点的右子树设置为当前结点的右子树的右子树
right = right.right;
//把当前结点的左子树设置为新结点的
left = newNode;
}
- 右旋
右旋就是左旋反过来
/**
* 右旋
*/
private void rightRotate(){
//创建一个新结点,值为当前结点的值
Node newNode = new Node(value);
//新结点的左/右子树
newNode.right = right;
newNode.left = left.right;
//当前结点改为左子结点
value = left.value;
left = left.left;
right = newNode;
}
- 完整代码
package com.company.tree.AVL;
/**
* Author : zfk
* Data : 10:49
*/
public class AVLTreeDemo {
public static void main(String[] args) {
int[] arr = {13,8,66,6,9,20,80,5,90};
//创建AVLTree
AVLTree avlTree = new AVLTree();
//添加结点
for (int i: arr){
avlTree.add(new Node(i));
}
//中序遍历
System.out.println("=== 中序遍历 ===");
avlTree.infixOrder();
System.out.println();
System.out.println("=== 平衡处理 ===");
System.out.println("树的高度:"+ avlTree.getRoot().height());
System.out.println("树的左子树的高度:"+ avlTree.getRoot().leftHeight());
System.out.println("树的右子树的高度:"+ avlTree.getRoot().rightHeight());
System.out.println("根节点为 : "+ avlTree.getRoot());
System.out.println("=== 删除结点 ===");
avlTree.delNodeBalance(8);
avlTree.infixOrder();
System.out.println();
System.out.println("=== 平衡处理 ===");
System.out.println("树的高度:"+ avlTree.getRoot().height());
System.out.println("树的左子树的高度:"+ avlTree.getRoot().leftHeight());
System.out.println("树的右子树的高度:"+ avlTree.getRoot().rightHeight());
System.out.println("根节点为 : "+ avlTree.getRoot());
System.out.println("根节点左结点为 : "+ avlTree.getRoot().left.value);
}
}
//AVL树
class AVLTree{
//根结点
private Node root;
public Node getRoot() {
return root;
}
//添加结点的方法
public void add(Node node){
if (root == null){
//空树,直接加入
root = node;
}
else {
//add方法递归加入
root.add(node);
}
}
//中序遍历
public void infixOrder(){
if (root != null){
root.infixOrder();
}
else {
System.out.println("=== 二叉排序树为空 ===");
}
}
//从根节点查找要删除的结点
public Node search(int value){
if (root == null){
return null;
}
else{
return root.search(value);
}
}
//从根节点查找父结点
public Node searchParent(int value){
if (root == null){
return null;
}
else {
return root.searchParent(value);
}
}
/**
* 删除右子树的最小结点并返回
* @param node 传入的结点,当做一个二叉排序树的根节点
* @return 返回以node为根结点的二叉排序树的最小结点
*/
public Node delRightTreeMin(Node node){
Node temp = node;
//循环查找左子结点,找到最小值
while (temp.left != null){
temp = temp.left;
}
//temp指向最小结点,删除并返回
delNode(temp.value);
return temp;
}
/**
* 删除结点:如果要删除的结点有一棵子树
* @param parent 要删除结点的父结点
* @param targetNode 要删除的结点
* @param value 要删除的结点的值
*/
public void delNodeOneTree(Node parent,Node targetNode,int value){
//如果要删除的结点只有左子树
if (targetNode.left != null){
if (parent != null) {
//如果targetNode是parent的左子结点
if (parent.left.value == value) {
parent.left = targetNode.left;
} else {
//targetNode是parent的右子结点
parent.right = targetNode.left;
}
}else {
//如果要删除的结点是根节点
root = targetNode.left;
}
}
//要删除的结点只有右子树
else {
if (parent != null) {
if (parent.left.value == value) {
parent.left = targetNode.right;
} else {
//targetNode是parent右子结点
parent.right = targetNode.right;
}
}
else {
//删除的是根节点
root = targetNode.right;
}
}
}
/**
* 删除结点:如果要删除的结点有左右子树
* @param parent 要删除结点的父结点
* @param targetNode 要删除的结点
*/
public Node delNodeTwoTree(Node parent,Node targetNode){
//得到右子树的最小值
Node min = delRightTreeMin(targetNode.right);
//将最小结点替换删除的结点,达成删除结点的效果
min.left = targetNode.left;
min.right = targetNode.right;
if (parent != null) {
if (parent.left == targetNode) {
parent.left = min;
} else {
parent.right = min;
}
}
return min;
}
/**
* 删除结点:如果要删除的结点为叶子结点
* @param parent 要删除结点的父结点
* @param value 要删除的结点的值
*/
public void delLeafNode(Node parent,int value){
//判断targetNode 是 父结点的左、右子结点
if (parent.left != null && parent.left.value == value){
//删除的结点是父结点的左子结点
parent.left = null;
}
else if (parent.right != null && parent.right.value == value){
//删除的结点是父结点的右子结点
parent.right = null;
}
}
//删除结点
private Node delNode(int value){
if (root == null){
return null;
}
//查找要删除的结点
Node targetNode = search(value);
//判断是否找到
if (targetNode == null){
return null;
}
//如果发现当前这棵二叉树只有一个结点,删除的就是根结点
if (root.left == null && root.right == null){
root = null;
return null;
}
//找父结点
Node parent = searchParent(value);
//如果要删除的结点是叶子结点
if (targetNode.left == null && targetNode.right == null){
delLeafNode(parent,value);
return parent;
}
//如果删除的结点有左右子树
else if (targetNode.left != null && targetNode.right != null){
Node node = delNodeTwoTree(parent, targetNode);
return node;
}
//删除只有一棵子树的结点
else {
delNodeOneTree(parent,targetNode,value);
return parent;
}
}
//删除结点并平衡
public void delNodeBalance(int value) {
Node node = delNode(value);
balance(node);
}
/**
* 删除结点后,需要回溯父结点,计算平衡因子,并平衡
*/
private void balance(Node node){
while (node != null){
node.judge();
node = searchParent(node.value);
}
}
}
//树结点
class Node{
int value;
//左右子结点
Node left;
Node right;
public Node(int value) {
this.value = value;
}
//返回左子树的高度
public int leftHeight(){
if (left == null){
return 0;
}
else {
return left.height();
}
}
//返回右子树的高度
public int rightHeight(){
if (right == null){
return 0;
}
else {
return right.height();
}
}
//当前结点的高度:该结点 为 根节点 的树的高度
public int height(){
//递归左子树、右子树,取最大值
return Math.max(left == null ? 0 : left.height(),right == null ? 0 : right.height()) + 1 ;
}
/**
* 左旋
*/
private void leftRotate(){
//创建一个新结点,为当前结点的值
Node newNode = new Node(value);
//把新的结点的左/右子树设置为当前结点的左子树
newNode.left = left;
newNode.right = right.left;
//把当前结点的值替换成右子树的值
value = right.value;
//把当前结点的右子树设置为当前结点的右子树的右子树
right = right.right;
//把当前结点的左子树设置为新结点的
left = newNode;
}
/**
* 右旋
*/
private void rightRotate(){
//创建一个新结点,值为当前结点的值
Node newNode = new Node(value);
//新结点的左/右子树
newNode.right = right;
newNode.left = left.right;
//当前结点改为左子结点
value = left.value;
left = left.left;
right = newNode;
}
/**
* 查找结点
* @param value 查找结点的值
* @return 要查找的结点,没找到返回null
*/
public Node search(int value){
if (value == this.value){
//找到就是该结点
return this;
}
else if (value < this.value){
//查找的值小于当前结点,向左子树递归查找
//如果存在左子结点
if (this.left != null) {
return this.left.search(value);
}
else {
//不存在左子结点,说明查找不到
return null;
}
}
else {
//查找的值大于当前的值,向右子树递归查找
//如果存在右子结点
if (this.right != null){
return this.right.search(value);
}
else{
//不存在右子结点,说明查找不到
return null;
}
}
}
/**
* 查找结点的父结点
* @param value 要查找结点的值
* @return 返回要查找的结点的父结点,没有就返回null
*/
public Node searchParent(int value){
//当前结点就是要删除的结点的父结点,就返回
if ((this.left != null && this.left.value == value) ||
(this.right != null && this.right.value == value) ){
return this;
}
else {
//如果查找的值小于当前结点
if (value < this.value && this.left != null){
//向左子树递归查找
return this.left.searchParent(value);
}
//如果查找的值大于等于当前结点
else if (value >= this.value && this.right != null){
//向右子树递归查找
return this.right.searchParent(value);
}else {
//都不满足,就找不到父结点
return null;
}
}
}
//按照二叉树排序树添加结点
public void add(Node node){
if (node == null){
return;
}
//判断传入结点值和当前子树的根结点的值的关系
//传入结点值小于当前子树根结点
if (node.value < this.value){
//根节点左子结点为空,就放在左子结点
if (this.left == null){
this.left = node;
}
else {
//不为空,递归向左子树添加
this.left.add(node);
}
}
//传入结点大于等于根结点
else {
if (this.right == null){
//右子结点为空,直接放入
this.right = node;
}
else {
//不为空,递归向右子树添加
this.right.add(node);
}
}
//当添加完一个结点,需要判断平衡
judge();
}
/**
* 判断平衡
*/
public void judge(){
//右子树高度 - 左子树高度 > 1 , 左旋
if (rightHeight() - leftHeight() > 1) {
//如果是不平衡情况是 : 插入右子树的左子树
if (right != null && right.leftHeight() > right.rightHeight()) {
//先对当前结点的右子树进行右旋
right.rightRotate();
//再对当前结点进行左旋
leftRotate();
} else {
leftRotate();
}
//直接退出即可
return;
}
//左子树高度 - 右子树高度 > 1 , 右旋
if (leftHeight() - rightHeight() > 1){
//如果是不平衡情况是 : 插入左子树的右子树
if (left != null && left.rightHeight() > left.leftHeight()) {
//先对当前结点的左子树 - 》 左旋
left.leftRotate();
//再对当前结点进行右旋
rightRotate();
}
else {
//直接右旋
rightRotate();
}
}
}
//中序遍历
public void infixOrder(){
if (this.left != null){
this.left.infixOrder();
}
System.out.print(this);
if (this.right != null){
this.right.infixOrder();
}
}
@Override
public String toString() {
return "[value=" + value+"]";
}
}
结果:
删除结点: