给你一个数列{1,2,3,4,5,6},要求创建一颗二叉排序树(BST), 并分析问题所在.
左边 BST 存在的问题分析:
1) 左子树全部为空,从形式上看,更像一个单链表.
2) 插入速度没有影响
3) 查询速度明显降低(因为需要依次比较), 不能发挥 BST的优势,因为每次还需要比较左子树,其查询速度比单链表还慢.
遇到以上情况,这时候就需要用我们今天的主角平衡二叉树来解决.
平衡二叉树的基本介绍:
1) 平衡二叉树也叫平衡 二叉搜索树(Self-balancing binary search tree)又被称为 AVL 树, 可以保证查询效率较高。
2) 具有以下特点:它是一 一 棵空树或 它的左右两个子树的高度差的绝对值不超过 1,并且 左右两个子树都是一棵平衡二叉树。平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、伸展树等
例如:下图中的第一颗树和第二颗树都是平衡二叉树,而最后一棵树不是
将一颗普通的二叉排序树变成平衡二叉树有三种方法:
第一种方法:如果该树的左子树高度-右子树高度>1就需要使用右旋转来平衡该树.
第二种方法:如果该树的右子树高度-左子树高度>1就需要使用左旋转来平衡该树.
第三种方法:如果该树满足第一种或者第二种方法的情况,但是单独使用一次左(右)旋转也不能将其变成平衡二叉树的时候,就需要使用双旋转。
双旋转也分为两种情况:
第一种:先左旋转再右旋转
第二种:先右旋转再左旋转
以下来介绍左(右)旋转的具体实现步骤
左旋转的实现步骤:
1、创建一个新节点,将当前树的根节点的值给它 2、让newNode的左子节点指向当前树的root的左子树 3、让newNode的右子节点指向当前树的root的右子树的左子树 4、让当前树的root的权值等于当前树的root的右子树的权值 5、让当前树的root的右子树指向当前树的root的右子树的右子树 6、让当前树的root的左子树指向newNode
代码如下:
/**
* 实现avl树的左旋转(右子树高度-1大于左子树高度)
* 思路:
*/
public void leftRotate(){
//1、创建一个新节点,将当前树的根节点的值给它
Node newNode = new Node(this.value);
//2、让newNode的左子节点指向当前树的root的左子树
newNode.left = this.left;
//3、让newNode的右子节点指向当前树的root的右子树的左子树
newNode.right = this.right.left;
//4、让当前树的root的权值等于当前树的root的右子树的权值
this.value = this.right.value;
//5、让当前树的root的右子树指向当前树的root的右子树的右子树
this.right = this.right.right;
//6、让当前树的root的左子树指向newNode
this.left = newNode;
}
右旋转的实现步骤:
1、创建一个新节点,将当前树的根节点的值给它 2、让newNode的左子节点指向当前树的root的右子树 3、让newNode的右子节点指向当前树的root的左子树的右子树 4、让当前树的root的权值等于当前树的root的左子树的权值 5、让当前树的root的左子树指向当前树的root的左子树的左子树 6、让当前树的root的右子树指向newNode
代码如下:
/**
* 实现avl树的右旋转(左子树高度-1大于右子树高度)
* 思路:
*/
public void rightRotate(){
//1、创建一个新节点,将当前树的根节点的值给它
Node newNode = new Node(this.value);
//2、让newNode的左子节点指向当前树的root的右子树
newNode.right = this.right;
//3、让newNode的右子节点指向当前树的root的左子树的右子树
newNode.left = this.left.right;
//4、让当前树的root的权值等于当前树的root的左子树的权值
this.value = this.left.value;
//5、让当前树的root的左子树指向当前树的root的左子树的左子树
this.left = this.left.left;
//6、让当前树的root的右子树指向newNode
this.right = newNode;
}
介绍双旋转的应用场景:
有些数列,进行单旋转(即一次旋转)就可以将非平衡二叉树转成平衡二叉树,但是在某些情况下,单旋转不能完成平衡二叉树的转换。比如数列
int[] arr = { 10, 11, 7, 6, 8, 9 }; 运行原来的代码可以看到,并没有转成 AVL 树.
解决思路分析:
第一种情况(先左再右):
1. 当树符合右旋转的条件时
2. 如果它的左子树的右子树高度大于它的左子树的高度
3. 先对当前这个结点的左节点进行左旋转
4. 然后再对当前结点进行右旋转的操作即可
第二种情况(先右再左):
1. 当树符合左旋转的条件时
2. 如果它的右子树的左子树高度大于它的右子树的高度
3. 先对当前这个结点的右节点进行右旋转
4. 然后再对当前结点进行左旋转的操作即可
以上介绍的两种情况都是属于双旋转的情况。
因为AVL树也要满足二叉排序树的要求,所以在添加节点的时候也要满足BST的要求,但同时还要满足平衡二叉树本身的要求,所以在插入节点的时候就需要进行判断是否需要进行旋转来平衡该树。所以我们还需要能求出以当前节点为根节点的树的高度和它的左子树或者右子树的高度。
获取树的高度的方法:
//求出左子树的高度
public int leftHeight() {
if (this.left == null) {
return 0;
}
return this.left.height();
}
//求出右子树的高度
public int rightHeight() {
if (this.right == null) {
return 0;
}
return this.right.height();
}
//求树的高度
public int height() {
return Math.max(this.left == null ? 0 : this.left.height(), this.right == null ? 0 : this.right.height()) + 1;
}
以下是AVL树添加节点的代码:
//递归的形式添加结点,注意需要满足二叉排序树的要求
public void add(Node node) {
if (node == null) {
return;
}
//判断传入的节点的权值,和当前节点的关系
if (node.value == this.value) {
if (this.left == null) {
this.left = node;
return;
}
if (this.right == null) {
this.right = node;
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);
}
}
// 当添加完一个结点后,如果: ( 右子树的高度- 左子树的高度) > 1 , 左旋转
if (this.rightHeight() - this.leftHeight() > 1){
//如果当前节点的右子树的左子树的高度大于当前节点的右子树的右子树高度,则需要对当前节点的右子节点先进行右旋转
if (this.right.leftHeight() > this.right.rightHeight()){
//先对当前节点的右子节点进行右旋转
this.right.rightRotate();
//再对当前节点进行左旋转
this.leftRotate();
}else {
//直接进行左旋转
this.leftRotate();
}
}
// 当添加完一个节点后,如果: (左子树高度 - 右子树高度) > 1 , 右旋转
if (this.leftHeight() - this.rightHeight() > 1){
//如果当前节点的左子树的右子树的高度大于当前节点的左子树的左子树高度,则需要对当前节点的左子节点先进行左旋转
if (this.left.rightHeight() > this.left.leftHeight()){
//对当前节点的左子树进行左旋转
this.left.leftRotate();
//再对当前节点进行右旋转
this.rightRotate();
}else {
//直接进行右旋转
this.rightRotate();
}
}
}
以下给出完整的代码:
Node:
public class Node {
public Integer value;
public Node left;
public Node right;
public Node(Integer value) {
this.value = value;
}
@Override
public String toString() {
return "Node{" +
"value=" + value +
'}';
}
/**
* 实现avl树的左旋转(右子树高度-1大于左子树高度)
* 思路:
*/
public void leftRotate(){
//1、创建一个新节点,将当前树的根节点的值给它
Node newNode = new Node(this.value);
//2、让newNode的左子节点指向当前树的root的左子树
newNode.left = this.left;
//3、让newNode的右子节点指向当前树的root的右子树的左子树
newNode.right = this.right.left;
//4、让当前树的root的权值等于当前树的root的右子树的权值
this.value = this.right.value;
//5、让当前树的root的右子树指向当前树的root的右子树的右子树
this.right = this.right.right;
//6、让当前树的root的左子树指向newNode
this.left = newNode;
}
/**
* 实现avl树的右旋转(左子树高度-1大于右子树高度)
* 思路:
*/
public void rightRotate(){
//1、创建一个新节点,将当前树的根节点的值给它
Node newNode = new Node(this.value);
//2、让newNode的左子节点指向当前树的root的右子树
newNode.right = this.right;
//3、让newNode的右子节点指向当前树的root的左子树的右子树
newNode.left = this.left.right;
//4、让当前树的root的权值等于当前树的root的左子树的权值
this.value = this.left.value;
//5、让当前树的root的左子树指向当前树的root的左子树的左子树
this.left = this.left.left;
//6、让当前树的root的右子树指向newNode
this.right = newNode;
}
//求出左子树的高度
public int leftHeight() {
if (this.left == null) {
return 0;
}
return this.left.height();
}
//求出右子树的高度
public int rightHeight() {
if (this.right == null) {
return 0;
}
return this.right.height();
}
//求树的高度
public int height() {
return Math.max(this.left == null ? 0 : this.left.height(), this.right == null ? 0 : this.right.height()) + 1;
}
//递归的形式添加结点,注意需要满足二叉排序树的要求
public void add(Node node) {
if (node == null) {
return;
}
//判断传入的节点的权值,和当前节点的关系
if (node.value == this.value) {
if (this.left == null) {
this.left = node;
return;
}
if (this.right == null) {
this.right = node;
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);
}
}
// 当添加完一个结点后,如果: ( 右子树的高度- 左子树的高度) > 1 , 左旋转
if (this.rightHeight() - this.leftHeight() > 1){
//如果当前节点的右子树的左子树的高度大于当前节点的右子树的右子树高度,则需要对当前节点的右子节点先进行右旋转
if (this.right.leftHeight() > this.right.rightHeight()){
//先对当前节点的右子节点进行右旋转
this.right.rightRotate();
//再对当前节点进行左旋转
this.leftRotate();
}else {
//直接进行左旋转
this.leftRotate();
}
}
// 当添加完一个节点后,如果: (左子树高度 - 右子树高度) > 1 , 右旋转
if (this.leftHeight() - this.rightHeight() > 1){
//如果当前节点的左子树的右子树的高度大于当前节点的左子树的左子树高度,则需要对当前节点的左子节点先进行左旋转
if (this.left.rightHeight() > this.left.leftHeight()){
//对当前节点的左子树进行左旋转
this.left.leftRotate();
//再对当前节点进行右旋转
this.rightRotate();
}else {
//直接进行右旋转
this.rightRotate();
}
}
}
//根据value寻找节点
public Node search(int value) {
if (this.value == value) {
return this;
} else if (value < this.value) {
if (this.left == null) {
return null;
}
return this.left.search(value);
} else {
if (this.right == null) {
return null;
}
return this.right.search(value);
}
}
//查找要删除节点的父节点
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 infixOrder() {
if (this.left != null) {
this.left.infixOrder();
}
System.out.print(this.value + " ");
if (this.right != null) {
this.right.infixOrder();
}
}
}
AVLTree:
public class AVLTree {
private Node root;
public Node getRoot() {
return root;
}
// 添加结点的方法
public void add(Node node) {
if (root == null) {
root = node;// 如果 root 为空则直接让 root 指向 node
} else {
root.add(node);
}
}
// 中序遍历
public void infixOrder() {
if (root != null) {
root.infixOrder();
} else {
System.out.println("二叉排序树为空,不能遍历");
}
}
/**
* 实现avl树的左旋转(右子树高度-1大于左子树高度)
*/
public void leftRotate(){
this.root.leftRotate();
}
/**
* 实现avl树的右旋转(左子树高度-1大于右子树高度)
*/
public void rightRotate(){
this.root.rightRotate();
}
// 查找要删除的结点
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);
}
}
}
AVLTreeDemo:
package com.atguigu.avl;
public class AVLTreeDemo {
public static void main(String[] args) {
//需要左旋转avl树
int[] arr = {4,3,6,5,7,8};
//需要双旋转的avl树
//int[] arrRight = { 10, 11, 7, 6, 8, 9 };
//创建AVLTree对象
AVLTree avlTree = new AVLTree();
//添加结点
for(int i=0; i < arr.length; i++) {
//在添加节点的时候已经进行了自平衡处理
avlTree.add(new Node(arr[i]));
}
System.out.println("中序遍历:"); // 3 4 5 6 7 8
avlTree.infixOrder();
System.out.println();
System.out.println("树的高度=" + avlTree.getRoot().height()); //3
System.out.println("树的左子树高度=" + avlTree.getRoot().leftHeight()); // 2
System.out.println("树的右子树高度=" + avlTree.getRoot().rightHeight()); // 2
System.out.println("当前的根结点=" + avlTree.getRoot());//6
//再加入几个节点进行验证自平衡二叉树
avlTree.add(new Node(10));
avlTree.add(new Node(11));
System.out.println("中序遍历:"); // 3 4 5 6 7 8 10 11
avlTree.infixOrder();
System.out.println();
System.out.println("树的高度=" + avlTree.getRoot().height()); //4
System.out.println("树的左子树高度=" + avlTree.getRoot().leftHeight()); // 2
System.out.println("树的右子树高度=" + avlTree.getRoot().rightHeight()); // 3
System.out.println("当前的根结点=" + avlTree.getRoot());//6
}
}