在进入本次课题时,我们要先了解什么是二叉排序树,不了解的伙伴可以看我的博客[数据结构与算法------树(树、二叉树、哈夫曼树)];了解的伙伴可以跳过直接进行的我们的课题(往下看)
我已经了解了完全二叉树、哈夫曼树、二叉排序树,为什么还要学习平衡二叉树呢?学习它的好处是什么?我们知道树的查找效率是由树的高度决定的,而平衡二叉树的作用是将树的高度达到最小,因此学习平衡二叉树可以提高程序的运行效率
平衡二叉树是什么?
它是一棵二叉排序树,但是它(父节点)的左右两个子树的高度差(平衡因子)的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树;如下图,它是否是一棵平衡二叉树?
从图中可以看出虽然根节点40左右子树的高度差绝对值<=1,但是它的左子树不是平衡二叉树(依次轮询节点,判断节点的左右子树的高度差,如节点30,它的左子树高度为2,右子树高度为0,它的左右子树的高度差绝对值!<=1),所以它不是一棵二叉平衡树
判断左右子树是否是一棵平衡二叉树,我们可以从左右子树的根节点(父节点)开始依次轮询,判断它们各个节点左右子树的高度差绝对值,如上图中,它的左子树为:
从根节点30开始依次轮询每个节点的左右子树的高度,节点30的左子树高度为2,右子树的高度为0(2减0大于1);节点20的左子树的高度为1,右子树的高度为0(1减0等于1);节点10的左子树的高度为0,右子树的高度为0(0减0等于0),因为节点30它的左右子树的高度差绝对值不满足<=1,所以它不是一棵二叉平衡树
它的右子树为:
从根节点50开始依次轮询每个节点的左右子树高度进行求差比较,节点50的左子树高度为0,右子树高度为1( |0-1|<=1 );节点60的左子树高度为0,右子树的高度为0( |0-0| <=1 ),因此它是一棵平衡二叉树
平衡二叉树的结构图
平衡二叉树的调整(添加、删除节点)
调整的原则:根据插入节点与失衡节点的位置关系来划分
LL旋转(左左旋转): 插入节点在失衡节点的左子树的左边,这时失衡节点只需经过一次左旋即可达到平衡
图一是一棵平衡二叉树,在节点10上添加左节点5(如图二),这时,根节点30的左子树的高度为3,右子树的高度为1( | 3-1 | > 1 ),因此节点30已经失衡了(不是平衡二叉树),我们需要调整这棵二叉树回到平衡状态,调整的方法是失衡节点30左旋一次,使这棵二叉树的左子树根节点20作为这棵二叉树的根节点,而节点20原来的右节点25作为失衡节点30的左节点(如图三)(作为左节点的原因:因为25比30小),这时这棵二叉树的左子树的高度为2,右子树的高度为2( | 2-2 | <=1 )同时左子树、右子树又是二叉平衡树,因此旋转后这棵树又变成了平衡二叉树
RR旋转(右右旋转): 插入节点在失衡节点的右子树的右边,这时失衡节点只需经过一次右旋即可达到平衡
原图(图一)是一棵平衡二叉树,在这棵平衡二叉树的右子树的右边添加节点50(图图二),这时这棵二叉树已经失衡了,因为这棵树的左子树的高度为1,右子树的高度为3(树的高度:从根节点出发的最大路径长度),它不符合左、右子树的高度差绝对值<=1;因此我们需要失衡节点20右旋一次使之达到平衡(如图三),图二右旋一次时,原来的根节点20作为这棵二叉树左子树的根节点,而原来右子树的根节点30作为这棵树的根节点,原来右子树的根节点30有左节点25,这时我们需要把这个节点放到左子树的右边(如图三),调整后发现,这时这棵树的左子树的高度为2,右子树的高度为2并且左、右子树都是平衡二叉树,因此这时这棵二叉树回到了平衡状态,即平衡二叉树
LR旋转(左右旋转): 插入节点在失衡节点的左子树的右边,这时我们需要在失衡节点左子树的根节点做一次右旋,然后在失衡节点再做一次左旋(一共两次旋转)
RL旋转(右左旋转): 插入节点在失衡节点的右子树的左边,这时我们需要在失衡节点的右子树的根节点做一次左旋,然后在失衡节点再做一次右旋(一共两次旋转)
LL旋转(左左旋转): 平衡二叉树节点的最大高度在左子树的左边时,删除节点在平衡二叉树节点的右子树的左边或右边,这时失衡节点只需经过一次左旋即可达到平衡
RR旋转(右右旋转): 平衡二叉树节点的最大高度在右子树的右边时,删除节点在平衡二叉树节点的左子树的左边或右边,这时失衡节点只需经过一次右旋即可达到平衡
LR旋转(左右旋转): 平衡二叉树节点的最大高度在左子树的右边时,删除节点在平衡二叉树节点的右子树的右边或左边,这时我们需要在失衡节点左子树的根节点做一次右旋,然后在失衡节点再做一次左旋(一共两次旋转)
RL旋转(右左旋转): 平衡二叉树节点的最大高度在右子树的左边时,删除节点在平衡二叉树节点的左子树的左边或右边,这时我们需要在失衡节点的右子树的根节点做一次左旋,然后在失衡节点再做一次右旋(一共两次旋转)
代码实现二叉平衡树节点的添加、删除
import java.util.ArrayList;
/**
* 平衡二叉树
*/
public class AVLTree {
//记录第一次打印
private static boolean print = true;
private static ArrayList<Integer> list = new ArrayList<>(); //存储节点数据
//节点
static class Node {
private int data; //数据
private Node parent; //父节点
private Node left; //左节点
private Node right; //右节点
private int height; //节点的高度
//带参构造
public Node(int data) {
this.data = data;
}
}
public static void printTree(Node node) {
if (node == null) {
System.out.println("空树");
} else {
if (print) { //判断是否是第一次打印树
System.out.println("[ root:" + node.data + ",高度:" + node.height + "]"); //输出这棵树的根节点和高度
print = false; //将print置成false
} else {
System.out.println(node.data + ",高度:" + node.height + "]"); //输出节点和节点的高度
}
if (node.left != null) { //如果左节点不为空
System.out.print("[ " + node.data + ".left:");
printTree(node.left); //递归
}
if (node.right != null) { //如果右节点不为空
System.out.print("[ " + node.data + ".right:");
printTree(node.right); //递归
}
}
}
//获取节点的高度
public static int getHeight(Node node) {
return node == null ? -1 : node.height; //三目运算,如果节点为null,则高度为-1,否则返回这个节点的高度
}
/**
* 平衡二叉树失衡的调整:
* LL旋转
* RR旋转
* LR旋转
* RL旋转
*/
//LL旋转:插入节点在失衡节点的左子树的左边,这时失衡节点只需经过一次左旋即可达到平衡
public static Node LLRotate(Node node) {
Node root = node.left; //记录失衡节点的左子树的根节点 root
node.parent = root; //节点父节点的变换
node.left = root.right; //将root的右子树作为失衡节点的左子树
root.right = node; //将失衡节点作为root的右子树
root.parent = null; //父节点的变换
//root、node高度调整
//因为Math.max()只是取最大值(左子树的高度或右子树的高度),如果要取节点这个的高度要+1
node.height = Math.max(getHeight(node.left), getHeight(node.right)) + 1;
root.height = Math.max(getHeight(root.left), node.height) + 1;
return root; //将root节点取代失衡节点的位置
}
//RR旋转:插入节点在失衡节点的右子树的右边,这时失衡节点只需经过一次右旋即可达到平衡
public static Node RRRotate(Node node) {
Node root = node.right; //记录失衡节点右子的根节点 root
node.parent = root; //父节点的变换
node.right = root.left; //将root的左子树作为失衡节点的右子树
root.left = node; //将失衡节点作为root的左子树
root.parent = null; //父节点的变换
//node、root的高度调整
node.height = Math.max(getHeight(node.left), getHeight(node.right)) + 1;
root.height = Math.max(node.height, getHeight(root.right)) + 1;
return root; //将root节点取代失衡节点的位置
}
//LR旋转:插入节点在失衡节点的左子树的右边,这时我们需要在失衡节点左子树的根节点做一次右旋,
// 然后在失衡节点再做一次左旋(一共两次旋转)
public static Node LRRotate(Node node) {
//失衡节点的左子树做右旋转,并将旋转后生成新的树作为失衡节点的左子树(为什么是左子树?因为是对失衡节点左子树进行旋转)
node.left = RRRotate(node.left);
//失衡节点做左旋转
return LLRotate(node);
}
//RL旋转:插入节点在失衡节点的右子树的左边,这时我们需要在失衡节点的右子树的根节点做一次左旋,
// 然后在失衡节点再做一次右旋(一共两次旋转)
public static Node RLRotate(Node node) {
//失衡节点的右子树做左旋转,并将旋转后生成新的树作为失衡节点的右子树
node.right = LLRotate(node.right);
//失衡节点做右旋转
return RRRotate(node);
}
public static Node insert(Node root, int data) {
if (root == null) { //如果传入的节点为null
return new Node(data); //返回新节点
}
//root:如果没有递归调用,则为树的根节点,否则为传入节点
if (data <= root.data) { //如果传入的数据<=传入节点的数据
root.left = insert(root.left, data); //生成新的节点并将这个节点作为root节点的左子树(递归方法)
root.left.parent = root; //节点的父节点
//判断是否失衡
int value = Math.abs(getHeight(root.left) - getHeight(root.right)); //root节点左子树高度和右子树高度差值的绝对值
if (value > 1) { //节点失衡
if (data <= root.left.data) { //插入节点在失衡节点左子树的左边
//LL旋转
System.out.println("进行了LL旋转");
root = LLRotate(root); //将旋转后返回的节点赋给根节点
} else { //插入节点在失衡节点左子树的右边
//LR旋转
System.out.println("进行了LR旋转");
root = LRRotate(root);
}
}
} else { //如果传入的数据> 传入节点的数据
root.right = insert(root.right, data); // //生成新的节点并将这个节点作为root节点的右子树(递归方法)
root.right.parent = root; //节点的父节点
int value = Math.abs(getHeight(root.left) - getHeight(root.right));
if (value > 1) { //失衡
if (data <= root.right.data) { //插入节点在失衡节点右子树的左边
//RL旋转
System.out.println("进行了RL旋转");
root = RLRotate(root);
} else { //插入节点在失衡节点右子树的左边
//RR旋转
System.out.println("进行了RR旋转");
root = RRRotate(root);
}
}
}
//设置节点的高度
root.height = Math.max(getHeight(root.left), getHeight(root.right)) + 1;
return root;
}
/**
* 删除节点
*/
private static Node del(Node root, int data) {
list.clear(); //将存储的数据清空
//遍历节点
list = saveData(root);
Node node = null;
if (list.contains(data)) { //如果节点包含这个数据
list.remove((Object) data); //要将int类型转换成Object类型,如果没有的话int就当作了下标
for (int i = 0; i < list.size(); i++) { //从新构建二叉树
node = insert(node, (Integer) list.get(i)); //构建平衡二叉树
}
}else {
System.out.println("二叉平衡树中没有数据为"+data+"的节点");
node = root;
}
return node;
}
//存储阶段数据
private static ArrayList saveData(Node root) {
if (root != null) {
list.add(root.data);
saveData(root.left); //遍历左子树
saveData(root.right); //遍历右子树
}
return list;
}
public static void main(String[] args) {
Node root = null;
root = insert(root, 30);
root = insert(root, 20);
root = insert(root, 40);
root = insert(root, 10);
root = insert(root, 25);
//插入节点在失衡结点的左子树的左边
root = insert(root, 5);
root = insert(root, 50);
root = del(root, 20);
root = del(root,1);
//打印树,按照先打印左子树,再打印右子树的方式
System.out.println();
System.out.println("二叉平衡树为:");
printTree(root);
}
}
结果截图: