平衡二叉树的插入
本文主要讲解平衡二叉树的节点插入时,怎么保证平衡,理解其旋转原理,对后续红黑树的学习也很重要
1.特性
对于树中任意一个节点,其左节点比右节点小,左子树高度与右子树高度差绝对值不能大于1。为此在插入元素时,AVL树使用左旋以及右旋来保证这个特性
2.旋转
AVL树插入时涉及四种旋转:右旋、左旋、先左后右、先右后左
2.1.右旋:左子作父,父为右子,右孙变左孙
通俗讲:当前节点因左子树高不平衡后,需要拉升左子树,即将其左子升为父,自己做其右节点(当前节点肯定比其左子大,做它右子很自然),若这个左子原本有右子(对当前节点而言就叫右孙了),这时左子得右节点就重复了嘛,因右孙肯定比左子大,要在左子升父后得右边,而当前节点已作为右子,所以这个右孙就只能挂在当前节点得左节点上了,即从右孙变成了左孙了
2.2.左旋:右子作父,父为左子,左孙变右孙(与右旋互为镜像)
2.3. 先左后右(先左旋,使其满足右旋的形状,再右旋)
某些情况,直接左右旋转并不能使其平衡,我们需要调整到适用左旋或右旋的情况,如下
2.4.先右后左(先右旋,使其满足左旋的形状,再左旋)
3.代码实战
1.AVLNode节点
package com.zg.demo.arithmetic.binarytree;
import lombok.Data;
/**
* AVLNode class
*
* @author zg
* @date 2021/3/22 17:33
*/
@Data
public class AVLNode {
private int value;
private int balance;
private int depth;
private AVLNode parentNode;
private AVLNode leftNode;
private AVLNode rightNode;
public AVLNode(int value) {
this.value = value;
depth = 1;
balance = 0;
}
public AVLNode(int value, AVLNode parentNode) {
this.value = value;
this.parentNode = parentNode;
depth = 1;
balance = 0;
}
public void insert(int value) {
insert(this, value);
}
/**
* 插入数据
* @param root 根节点
* @param value 插入值
*/
private void insert(AVLNode root, int value) {
if (value < root.value) {
if (root.leftNode != null) {
insert(root.leftNode, value);
} else {
root.leftNode = new AVLNode(value, root);
}
} else {
if (root.rightNode != null) {
insert(root.rightNode, value);
} else {
root.rightNode = new AVLNode(value, root);
}
}
root.balance = calcBalance(root);
// 左子树高,需要右旋
if (root.balance > 1) {
// 左子节点右子树高先左旋,旋转成符合右旋的结构
if (root.leftNode.balance < 0) {
leftRotate(root.leftNode);
}
rightRotate(root);
return;
}
// 右子树高,需要左旋
if (root.balance < -1) {
// 右子节点左子树高先右旋,旋转成符合左旋的结构
if (root.rightNode.balance > 0) {
rightRotate(root.rightNode);
}
leftRotate(root);
return;
}
root.balance = calcBalance(root);
root.depth = calcDepth(root);
}
/**
* 计算节点平衡因子,左右子节点都平衡则当前节点平衡
* @param avlNode 节点
* @return int
*/
private int calcBalance(AVLNode avlNode) {
int leftDepth = 0;
int rightDepth = 0;
if (avlNode.leftNode != null) {
leftDepth = avlNode.leftNode.depth;
}
if (avlNode.rightNode != null) {
rightDepth = avlNode.rightNode.depth;
}
return leftDepth - rightDepth;
}
/**
* 计算节点高度,当前节点树高度
* @param avlNode 节点
* @return int
*/
private int calcDepth(AVLNode avlNode) {
int depth = 0;
if (avlNode.leftNode != null) {
depth = avlNode.leftNode.depth;
}
if (avlNode.rightNode != null && avlNode.rightNode.depth > depth) {
depth = avlNode.rightNode.depth;
}
return ++depth;
}
/**
* 右旋,因左子树高度比右子树高度大于1,破坏平衡二叉树结构
* 因是左子树高了,需拉升左子树下节点,即:
* 左子作父,父做右子,右孙变左孙
* @param root 节点
* 5 3
* / \ / \
* 3 8 2 5
* / \ / / \
* 2 4 1 4 8
* /
* 1
*/
private void rightRotate(AVLNode root) {
// 左子
AVLNode leftNode = root.leftNode;
// 父
AVLNode parentNode = root.parentNode;
// 右孙
AVLNode rightGrandSon = leftNode.rightNode;
// 左子做父
// 当前节点不是根节点,更改当前节点父节点引用
if (parentNode != null) {
// 判断当前节点在其父节点下是左节点还是右节点
if (root == parentNode.leftNode) {
parentNode.leftNode = leftNode;
}
if (root == parentNode.rightNode) {
parentNode.rightNode = leftNode;
}
}
leftNode.parentNode = parentNode;
root.parentNode = leftNode;
// 父做右子
leftNode.rightNode = root;
// 右孙变左孙,即左子做父后,其原本的右节点(右孙),需要转移到右子树做左孙,即做当前节点的左子节点
root.leftNode = rightGrandSon;
if (rightGrandSon != null) {
rightGrandSon.parentNode = root;
}
// 重新计算高度和平衡因子
root.depth = calcDepth(root);
root.balance = calcBalance(root);
leftNode.depth = calcDepth(leftNode);
leftNode.balance = calcBalance(leftNode);
}
/**
* 左旋 与右旋对称
* 右子作父,父做左子,左孙变右孙
* @param root 节点
* 5 7
* / \ / \
* 3 7 5 8
* / \ / \ \
* 6 8 3 6 9
* \
* 9
*/
private void leftRotate(AVLNode root) {
// 右子
AVLNode rightNode = root.rightNode;
// 父
AVLNode parentNode = root.parentNode;
// 左孙
AVLNode leftGrandSon = rightNode.leftNode;
if (parentNode != null) {
if (parentNode.rightNode == root) {
parentNode.rightNode = rightNode;
}
if (parentNode.leftNode == root) {
parentNode.leftNode = rightNode;
}
}
rightNode.parentNode = parentNode;
root.parentNode = rightNode;
rightNode.leftNode = root;
root.rightNode = leftGrandSon;
if (leftGrandSon != null) {
leftGrandSon.parentNode = root;
}
root.depth = calcDepth(root);
root.balance = calcBalance(root);
rightNode.depth = calcDepth(rightNode);
rightNode.balance = calcBalance(rightNode);
}
@Override
public String toString() {
return "AVLNode{" +
"value=" + value +
", balance=" + balance +
", depth=" + depth +
'}';
}
}
2.Tree
package com.zg.demo.arithmetic.binarytree;
import lombok.Data;
/**
* AVLTree class
*
* @author zg
* @date 2021/4/1 10:25
*/
@Data
public class AVLTree {
private AVLNode root;
public void insertNode(int value) {
if (root == null) {
root = new AVLNode(value);
return;
}
root.insert(value);
setRootNode(root);
}
private void setRootNode(AVLNode node) {
if (node.getParentNode() == null) {
this.setRoot(node);
return;
}
setRootNode(node.getParentNode());
}
}
3.test
public static void main(String[] args) {
//平衡二叉树
AVLTree avlTree = new AVLTree();
for (int i = 0; i < 12; i++) {
avlTree.insertNode(i);
}
// 层次打印方便查看
printLevel(avlTree.getRoot());
}
private static void printLevel(AVLNode root) {
LinkedBlockingQueue<AVLNode> queue = new LinkedBlockingQueue<>();
queue.add(root);
List<List<Integer>> lists = new ArrayList<>();
lists.add(new ArrayList<>());
while (!queue.isEmpty()) {
AVLNode currentNode = queue.poll();
int level = getLevel(currentNode, 0);
if (lists.size() == level) {
lists.add(new ArrayList<>());
}
List<Integer> list1 = lists.get(level);
list1.add(currentNode.getValue());
if (currentNode.getLeftNode() != null) {
queue.add(currentNode.getLeftNode());
}
if (currentNode.getRightNode() != null) {
queue.add(currentNode.getRightNode());
}
}
lists.forEach(integers -> {
integers.forEach(v -> {
System.out.print(v);
System.out.print(" ");
});
System.out.println();
});
}
private static int getLevel(AVLNode node, Integer level) {
if (node.getParentNode() != null) {
return getLevel(node.getParentNode(), ++level);
}
return level;
}