数据结构复习之AVL树(AVL树的概念、左旋、右旋操作保证平衡)
一、平衡查找二叉树
由于二叉查找树在某些极端情况下可能会形成一个链表,使其性能大大降低,故AVL树作为对其的一种改进
AVL改进的方式,数据的插入仍然遵循二叉查找树的特性,但是需要在元素插入时候保证其左右子树的高度之差<=1,如果不满足,则需要通过旋转来保证树的平衡性
二、如何旋转
对于元素插入导致AVL树失衡的情况有四种
故需要针对四种情况分别设计平衡旋转方式,在插入元素时进行检查
-
LL:在node的左子结点的左子结点插入元素导致失衡
-
RR:在node的右子结点的右子结点插入元素导致失衡
-
LR:在node的左子结点的右子结点插入元素导致失衡
-
RL:在node的右子结点的左子结点插入元素导致失衡
旋转方式针对以上情况分为四种
左旋->LL
//1)记录不平衡结点的左子结点
Node node_left = node.left;
//2)将左子结点的右子结点作为不平衡结点的左子结点
node.left = node_left.right;
//3)将不平衡结点作为其左子结点的右子结点
node_left.right = node;
右旋->RR
//1)临时结点指向失衡结点的右子结点
Node node_right = node.right;
//2)失衡结点的右子结点指向临时结点的左子结点
node.right = node_right.left;
//3)临时结点的左子结点指向失衡结点
node_right.left = node;
左右旋->LR
//1)可以拆分为一次对失衡结点的左子结点的右旋操作
node.left = rightRotate(node.left);
//2)之后进行对失衡结点的一次左旋操作
node = leftRotate(node);
右左旋->RL
//1)对失衡结点的右子结点进行左旋
node.right = leftRotate(node.right);
//2)对失衡结点进行右旋
node = rightRotate(node);
三、参考代码
package TreeTest;
/**
* AVL树 平衡查找二叉树,左右子树的高度差小于等于1,树的高度为左右子树高度的最大值+1
* 为了解决由于二叉查找树可能会出现6 5 4 3 2 1的类似单链表的情况,降低效率的问题
* 当二叉查找树出现了高度差为2的时候需要对二叉查找树进行平衡化
* 平衡化的情况分为:LL LR RR RL四种
* 分别对应:
* LL:在node的左子结点的左子结点插入元素导致失衡
* RR:在node的右子结点的右子结点插入元素导致失衡
* LR:在node的左子结点的右子结点插入元素导致失衡
* RL:在node的右子结点的左子结点插入元素导致失衡
* 左旋:简单来说将node的左子结点作为当前树的根结点
* 右旋:将node的右子结点作为当前树的根结点
*/
public class AVLTree {
private Node root;
public static void main(String[] args) {
AVLTree avlTree = new AVLTree();
//左旋测试
//int[] arr = {8,4,6,2,1,12};
//右旋测试
//int[] arr = {8,4,12,10,14,13};
//LR旋转测试
//int[] arr = {8,4,12,2,6,5};
//RL旋转测试
int[] arr = {8,4,12,10,14,11};
for (int i : arr) {
avlTree.put(i);
}
System.out.println("整棵树的高度:"+avlTree.getRootHeight());
System.out.println("root左子树高度:"+avlTree.getNodeHeight(avlTree.root.left));
System.out.println("root右子树高度:"+avlTree.getNodeHeight(avlTree.root.right));
avlTree.midErgodic();
}
/**
* LL 左旋处理树不平衡的情况
* 1)记录不平衡结点的左子结点
* 2)将左子结点的右子结点作为不平衡结点的左子结点
* 3)将不平衡结点作为其左子结点的右子结点
* 4)将不平衡结点的左子结点作为当前树的根结点(一切围绕此展开)
*/
public Node leftRotate(Node node){
//1)记录不平衡结点的左子结点
Node node_left = node.left;
//2)将左子结点的右子结点作为不平衡结点的左子结点
node.left = node_left.right;
//3)将不平衡结点作为其左子结点的右子结点
node_left.right = node;
//4)将不平衡结点的左子结点作为当前树的根结点(一切围绕此展开)
return node_left;
}
/**
* RR 右旋处理
* 1)临时结点指向失衡结点的右子结点
* 2)失衡结点的右子结点指向临时结点的左子结点
* 3)临时结点的左子结点指向失衡结点
* 4)将临时结点提升为当前根结点
*/
public Node rightRotate(Node node){
//1)临时结点指向失衡结点的右子结点
Node node_right = node.right;
//2)失衡结点的右子结点指向临时结点的左子结点
node.right = node_right.left;
//3)临时结点的左子结点指向失衡结点
node_right.left = node;
//4)将临时结点提升为当前根结点
return node_right;
}
/**
* LR 左右旋转:由于在左子树的右子树上插入元素导致失衡,所以称为LR旋转
* 步骤:
* 1)可以拆分为一次对失衡结点的左子结点的右旋操作
* 2)之后进行对失衡结点的一次左旋操作
*/
public Node leftRightRotate(Node node){
//1)可以拆分为一次对失衡结点的左子结点的右旋操作
node.left = rightRotate(node.left);
//2)之后进行对失衡结点的一次左旋操作
node = leftRotate(node);
return node;
}
/**
* RL 右左旋转:由于右子树的左子树插入结点导致失衡
* 步骤:
* 1)对失衡结点的右子结点进行左旋
* 2)对失衡结点进行右旋
*/
public Node rightLeftRotate(Node node){
//1)对失衡结点的右子结点进行左旋
node.right = leftRotate(node.right);
//2)对失衡结点进行右旋
node = rightRotate(node);
return node;
}
/**
* 向树中插入元素(重载方法)
* @param data
*/
public void put(int data){
root = put(data,root);
}
/**
* 向树中插入元素
* @param data 待插入的元素
* @param node 需要检查的结点
* @return 插入成功后的树结构
*/
public Node put(int data,Node node){
if(node == null){
return new Node(data,null,null);
}
//如果不为空 开始寻找要插入的位置
if(data < node.data){
node.left = put(data,node.left);
}else if (data > node.data){
node.right = put(data,node.right);
}else{
System.out.println("要插入的元素已存在");
return null;
}
//元素插入成功后,逐层向上寻找失衡结点
if (getNodeHeight(node.left) - getNodeHeight(node.right) > 1){
//如果插入的结点数据大于失衡结点的左子树的值,证明该结点是在失衡结点的左子树的右子树插入,需要左旋右旋
if(data > node.left.data){
node = leftRightRotate(node);
}else{
//否则证明插入的数据位于失衡结点的左子树的左子树,左旋即可
node = leftRotate(node);
}
}else if (getNodeHeight(node.right) - getNodeHeight(node.left) > 1){
//如果插入的结点数据小于失衡结点的右子树的值,证明该节点是在失衡结点的右子树的左子树插入,需要右旋左旋
if(data < node.right.data){
node = rightLeftRotate(node);
}else{
//否则证明插入的数据位于失衡结点的右子树的右子树,右旋即可
node = rightRotate(node);
}
}
return node;
}
/**
* 获取根结点的树的高度
* @return
*/
public int getRootHeight(){
return getNodeHeight(root);
}
/**
* 获取当前结点的树的高度
* 如果当前结点为null,则高度为0,否则继续递归判断高度
* @param node
* @return
*/
public int getNodeHeight(Node node){
if(node == null){
return 0;
}
int leftHeight = node.left == null ? 0:getNodeHeight(node.left);
int rightHeight = node.right == null ? 0 : getNodeHeight(node.right);
return Math.max(leftHeight,rightHeight)+1;
}
/**
* 重载中序遍历方法,方便调用
*/
public void midErgodic(){
midErgodic(root);
}
/**
* 平衡树的中序遍历
*/
public void midErgodic(Node node){
if(node == null){
return ;
}
midErgodic(node.left);
System.out.println(node.data);
midErgodic(node.right);
}
/**
* 结点结构定义
*/
public class Node{
private int data;
private Node left;
private Node right;
private int height;
public Node(int data, Node left, Node right, int height) {
this.data = data;
this.left = left;
this.right = right;
this.height = height;
}
public Node(int data, Node left, Node right) {
this(data,left,right,0);
}
}
}