目录
右旋
平衡二叉树的引入
给一个数列{1,2,3,4,5,6},要求创建一颗二叉排序树(BST),并分析问题所在。
问题分析:
该二叉排序树的左子树全部为空,从形式上看更像一个单链表。插入与删除的速度不受影响,但是,查询的速度明显较低(因为要依次进行比较,每次还需要比较左子树),查询速度明显低于单链表。
解决方法:使用平衡二叉树(AVL树)
平衡二叉树的基本概念
1.平衡二叉树也称平衡二叉搜索树或者AVL树,可以保证查询效率较高。
2.平衡二叉树的前提是一个二叉排序树(二叉搜索树)。
3.平衡二叉树的特点:它是一棵空树或者它的左右两个子树的高度差的绝对值不超过1,并且它的左右两颗子树也都是一颗平衡二叉树。
4.平衡二叉树的常用的实现方法:红黑树、AVL、替罪羊树、Treap、伸展树等。
平衡二叉树的高度求解
//返回当前结点的左子树的高度
public int leftHeight() {
if(left != null) {
return left.height();
} else {
return 0;
}
}
//返回当前结点的右子树的高度
public int rightHeight() {
if(right != null) {
return right.height();
} else {
return 0;
}
}
//返回以该结点为根结点的树的高度
public int height() {
return Math.max(left == null ? 0 : left.height(),right == null ? 0 : right.height()) + 1;
}
平衡二叉树的创建(节点的添加插入)
给定某一数列,要求根据此数列创建一个平衡二叉树。当依次向树中添加某一节点后,左子树和右子树的高度差的绝对值 > 1,则此时就不再是一个平衡二叉树。如何处理?
左旋
在添加节点过程中会出现:
添加完某一节点后,发现此时左子树的高度 - 右子树的高度 > 1,此时如何解决?
进行左旋:【降低右子树的高度,增加左子树的高度】
思想:
1.创建一个新的节点newNode,值等与当前结点的值。
2.把新节点的左子树设置为当前结点的左子树
newNode.left = left;
3.把新节点的右子树设置成当前结点的右子结点的左子树
newNode.right = right.left;
4.把当前结点的值更换成为当前结点的右子结点的值
value = right.value;
5.把当前节点的左子树设置成以新节点为根结点的树
left = newNode;
图解:
代码实现:
/**
* 使用左旋转的时机: 添加完一个节点后,就判断:树的右子树的高度 - 左子树的高度是否大于1:
* 如果大于1,则进行左旋转
* 左旋转的方法:
*/
public void leftRotate() {
//1.创建一个新的节点,值等于当前结点的值
Node newNode = new Node(this.value);
//2.把新节点的左子树设置成为当前结点的左子树
newNode.left = this.left;
//3.把新节点的右子树设置成为当前结点的右子结点的左子树
newNode.right = this.right.left;
//4.将当前结点的值设置成为当前结点的右子结点的值
this.value = this.right.value;
//5.将当前结点的右子树设置为当前结点的右子树的右子树
this.right = this.right.right;
//6.将当前结点的左子结点设置为以新节点为根结点的树
this.left = newNode;
}
右旋
给定数列{10,12,8,9,7,6},创建一个平衡二叉树?
在添加节点过程中会出现:
添加完某一节点后,发现此时右子树的高度-左子树的高度 > 1,此时如何解决?
解决方法:进行右旋【降低左子树的高度,增加右子树的高度】
思想:
1.创建一个新节点newNode,值等于当前结点的值;
2.把新节点的右子树设置成当前结点的右子树;
newNode.right = right;
3.把新节点的左子树设置成当前结点的左子结点的右子树;
newNode.left = left.right;
4.把当前结点的值更改成当前结点的左子结点的值;
value = left.value;
5.把当前结点的左子树设置成当前结点的左子结点的左子树;
left = left.left;
6.把当前结点的右子树设置成以新节点newNode为根结点的树。
right = newNode;
图解:
代码实现:
/**
* 使用右旋转的时机: 添加完一个节点后,就判断:树的左子树的高度 - 右子树的高度是否大于1:
* 如果大于1,则进行右旋转
* 右旋转的方法:
*/
public void rightRotate() {
//1.创建一个新节点newNode,值等于当前结点的值
Node newNode = new Node(this.value);
//2.将新节点的右子树设置成当前结点的右子树
newNode.right = this.right;
//3.将新节点的左子树设置成当前结点的左子结点的右子树
newNode.left = this.left.right;
//4.将当前结点的值更改为当前节点的左子结点的值
this.value = this.left.value;
//5.将当前结点的左子树设置成当前节点的左子结点的左子树
this.left = this.left.left;
//6.将当前结点的右子树设置成以新节点newNode为根节点的树
this.right = newNode;
}
添加节点:
在添加某一节点后如何判断判断出需要进行左旋或者右旋?
思路:
① 添加完某一节点后,如果发现此时左子树的高度 - 右子树的高度 > 1,那么需要对以当前结点为根节点的树调用右旋,但是在调用右旋之前应当考虑:
如果当前节点的左子结点的右子树的高度 >当前节点的左子结点的左子树的高度,那么我们此时需要对以当前节点的左子结点为根结点的树进行左旋。(如果不这样做,就会导致即使对以当前结点为根结点的树进行右旋,但调整后的树也不是一个AVL树)
② 添加完某一节点后,如果发现此时右子树的高度 - 左子树的高度 > 1,那么需要对以当前结点为根结点的树调用左旋,但是在调用左旋之前应当考虑:
如果当前节点的右子结点的左子树的高度 >当前节点的右子结点的右子树的高度,那么我们此时需要对以当前节点的右子结点为根结点的树进行右旋。(如果不这样做,就会导致即使对以当前结点为根结点的树进行左旋,但调整后的树也不是一个AVL树)
图解:
代码实现:
//当添加完一个节点后,判断树的左右子树的高度差的绝对值是否超过1:
//如果右子树的高度 - 左子树的高度 > 1,则进行左旋转:
if(this.rightHeight() - this.leftHeight() > 1) {
//如果当前结点的右子结点的左子树的高度 > 当前结点的右子结点的右子树的高度,则对当前节点的右子结点进行右旋转
if(this.right != null && this.right.leftHeight() > this.right.rightHeight()) {
this.right.rightRotate();
}
this.leftRotate();
return; //第一种情况已经解决,不用再考虑另外一种情况
}
//如果左子树的高度 - 右子树的高度 > 1,则进行右旋转:
if(this.leftHeight() - this.rightHeight() > 1) {
//如果当前结点的左子结点的右子树的高度 > 当前结点的左子结点的左子树的高度,则对当前结点的左子结点进行左旋转
if(this.left != null && this.left.rightHeight() > this.left.leftHeight()) {
this.left.leftRotate();
}
this.rightRotate();
}
AVL树的创建(节点的添加)整体代码:
/**
* 添加插入节点
* @param value
*/
public void add(int value) {
//1.添加节点
if(value < this.value) {
if(this.left == null) {
this.left = new Node(value);
} else {
this.left.add(value);
}
} else {
if(this.right == null) {
this.right = new Node(value);
} else {
this.right.add(value);
}
}
//2.当添加完一个节点后,判断树的左右子树的高度差的绝对值是否超过1:
//如果右子树的高度 - 左子树的高度 > 1,则进行左旋转:
if(this.rightHeight() - this.leftHeight() > 1) {
//如果当前结点的右子结点的左子树的高度 > 当前结点的右子结点的右子树的高度,则对当前节点的右子结点进行右旋转
if(this.right != null && this.right.leftHeight() > this.right.rightHeight()) {
this.right.rightRotate();
}
this.leftRotate();
return; //第一种情况已经解决,不用再考虑另外一种情况
}
//如果左子树的高度 - 右子树的高度 > 1,则进行右旋转:
if(this.leftHeight() - this.rightHeight() > 1) {
//如果当前结点的左子结点的右子树的高度 > 当前结点的左子结点的左子树的高度,则对当前结点的左子结点进行左旋转
if(this.left != null && this.left.rightHeight() > this.left.leftHeight()) {
this.left.leftRotate();
}
this.rightRotate();
}
}
平衡二叉树节点的删除、查询、遍历
与二叉排序树节点的删除、查询、遍历思想代码相同。具体点击二叉排序树_小何小何h的博客-CSDN博客
平衡二叉树完整汇总代码
import org.junit.Test;
/**
* AVL树(平衡二叉树):
*
*/
public class AVLTreeDemo {
@Test
public void test1() {
//左旋测试:
int arr1[] = {4,3,6,5,7,8};
System.out.println("左旋:");
AVLTree avlTree1 = new AVLTree();
for(int value : arr1) {
avlTree1.add(value);
}
avlTree1.preOrder();//正确为:6 4 3 5 7 8
}
@Test
public void test2() {
//右旋测试:
int arr2[] = {10,12,8,9,7,6};
System.out.println("右旋:");
AVLTree avlTree2 = new AVLTree();
for(int value : arr2) {
avlTree2.add(value);
}
avlTree2.preOrder(); //正确为:8 7 6 10 9 12
}
@Test
public void test3() {
//双旋测试1:
int arr3[] = {10,11,7,6,8,9};
System.out.println("双旋1:");
AVLTree avlTree3 = new AVLTree();
for(int value : arr3) {
avlTree3.add(value);
}
avlTree3.preOrder();//正确为:8 7 6 10 9 11
}
@Test
public void test4() {
//双旋测试2:
int arr4[] = {2,1,6,5,7,3};
System.out.println("双旋2:");
AVLTree avlTree4 = new AVLTree();
for(int value : arr4) {
avlTree4.add(value);
}
avlTree4.preOrder();//正确为:5 2 1 3 6 7
}
}
//创建AVL树
class AVLTree {
private Node root;
public AVLTree() {
}
public AVLTree(Node root) {
this.root = root;
}
public Node getRoot() {
return root;
}
/**
* 平衡二叉树的创建(结点的添加):
*/
public void add(int value) {
if(root == null) {
root = new Node(value);
}else{
root.add(value);
}
}
/**
* 平衡二叉树结点的删除:三种情况:
* 1) 如果要删除的节点是一个叶子节点,则:直接删除该结点;
* 2) 如果要删除的节点只有一个子结点,则:替换要删除的节点为其子结点;
* 3) 如果要删除的节点有两个子结点,则:先找到以要删除的节点的为根节点的右子树中最小的结点,将要删除的结点的值替换成最小结点的值,删除最小结点。
* (或者首先找到以要删除的节点的为根节点的左子树中最大的结点,后面的操作相同)
*
*/
public void deleteNode(int value) {
if(root == null) {
System.out.println("当前树为空,无法删除指定值的节点");
return;
} else {
//找要删除的结点;
Node targetNode = search(value);
if(targetNode == null) {
System.out.printf("该树中没有值为%d的节点,无法删除", value);
System.out.println();
return;
}
//如果只有一个根节点,且就是要删除的节点,就让树为空即可;否则就结束删除
if (root.left == null && root.right == null) {
if (value == root.value) {
root = null;
return;
}
}
//找要删除节点的父结点:(另外写一个方法,直接调用)
Node parent = searchParent(value);
//第一种情况:
/*如果删除的是叶子结点,则直接删除。步骤:
1、找到要删除的结点
2、找到要删除的结点的父结点
3、判断要删除的节点是父结点的什么节点?如果是左子结点:parent.left = null;反之,则:parent.right = null;
*/
//第一种情况
if(targetNode.left == null && targetNode.right == null) {
if(parent.left != null && parent.left.value == targetNode.value) {
parent.left = null;
}
if(parent.right != null && parent.right.value == targetNode.value) {
parent.right = null;
}
return;
}
/*如果删除的是只有一个子节点的节点,则替换要删除的节点为其子结点。步骤:
1.查找要删除的结点
2.查找要删除的结点的父结点
3.判断要删除的节点是父结点的什么节点?
4.判断要删除的结点的唯一子结点是它的左子结点还是右子结点?
*/
//第二种情况
if((targetNode.left != null && targetNode.right == null ) ||(targetNode.left == null && targetNode.right != null)){
if(parent.left.value == targetNode.value) {
if(targetNode.left != null) {
parent.left = targetNode.left;
} else {
parent.left = targetNode.right;
}
} else {
if(targetNode.left != null) {
parent.right = targetNode.left;
} else {
parent.right = targetNode.right;
}
}
} else {
/*第三种情况:
如果删除的是有两个子结点的结点,则在以要删除的结点为根结点的右子树的最小结点,再将其值保存在临时变量temp中,
将要删除的结点的值替换成temp,将最小结点删除。
*/
targetNode.value = RightMixNode(new Node(value).right);
}
}
}
//查找以删除的结点为根节点的右子树中的最小结点
public int RightMixNode(Node node) {
int temp = 0;
while (node.left != null) {
node = node.left;
temp = node.value;
}
deleteNode(node.value);
return temp;
}
//查找要删除节点的父结点
public Node searchParent(int value) {
if(root == null) {
return null;
}
Node parent = root.searchParent(value);
return parent;
}
/**
* 平衡二叉树的查找:
*/
public Node search(int value) {
if(root == null) {
return null;
} else {
return root.search(value);
}
}
/**
* 平衡二叉树的遍历:
*/
//前序遍历
public void preOrder() {
if(root == null) {
System.out.println("树为空,无法完成前序遍历");
} else {
root.preOrder();
}
}
//中序遍历
public void infixOrder() {
if(root == null) {
System.out.println("树为空,无法完成中序遍历");
} else {
root.infixOrder();
}
}
//后序遍历
public void postOrder() {
if(root == null) {
System.out.println("树为空,无法完成后序遍历");
} else {
root.postOrder();
}
}
}
//创建一个节点类
class Node {
int value;
Node left; //左指针
Node right;//右指针
public Node() {
}
public Node(int value) {
this.value = value;
}
@Override
public String toString() {
return "Node{" +
"value=" + value +
'}';
}
/**
* 添加插入节点
* @param value
*/
public void add(int value) {
//1.添加节点
if(value < this.value) {
if(this.left == null) {
this.left = new Node(value);
} else {
this.left.add(value);
}
} else {
if(this.right == null) {
this.right = new Node(value);
} else {
this.right.add(value);
}
}
//2.当添加完一个节点后,判断树的左右子树的高度差的绝对值是否超过1:
//如果右子树的高度 - 左子树的高度 > 1,则进行左旋转:
if(this.rightHeight() - this.leftHeight() > 1) {
//如果当前结点的右子结点的左子树的高度 > 当前结点的右子结点的右子树的高度,则对当前节点的右子结点进行右旋转
if(this.right != null && this.right.leftHeight() > this.right.rightHeight()) {
this.right.rightRotate();
}
this.leftRotate();
return; //第一种情况已经解决,不用再考虑另外一种情况
}
//如果左子树的高度 - 右子树的高度 > 1,则进行右旋转:
if(this.leftHeight() - this.rightHeight() > 1) {
//如果当前结点的左子结点的右子树的高度 > 当前结点的左子结点的左子树的高度,则对当前结点的左子结点进行左旋转
if(this.left != null && this.left.rightHeight() > this.left.leftHeight()) {
this.left.leftRotate();
}
this.rightRotate();
}
}
/**
* 删除节点
*
*/
//查找要删除节点的父结点
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.left.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 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;
}
}
}
/**
* 遍历:
*/
//前序遍历
public void preOrder(){
System.out.println(this);
if(this.left != null) {
this.left.preOrder();
}
if(this.right != null) {
this.right.preOrder();
}
}
//中序遍历
public void infixOrder() {
if(this.left != null) {
this.left.infixOrder();
}
System.out.println(this);
if(this.right != null) {
this.right.infixOrder();
}
}
//后序遍历
public void postOrder() {
if (this.left != null) {
this.left.postOrder();
}
if (this.right != null) {
this.right.postOrder();
}
System.out.println(this);
}
//返回当前结点的左子树的高度
public int leftHeight() {
if(left != null) {
return left.height();
} else {
return 0;
}
}
//返回当前结点的右子树的高度
public int rightHeight() {
if(right != null) {
return right.height();
} else {
return 0;
}
}
//返回以该结点为根结点的树的高度
public int height() {
return Math.max(left == null ? 0 : left.height(),right == null ? 0 : right.height()) + 1;
}
/**
* 使用左旋转的时机: 添加完一个节点后,就判断:树的右子树的高度 - 左子树的高度是否大于1:
* 如果大于1,则进行左旋转
* 左旋转的方法:
*/
public void leftRotate() {
//1.创建一个新的节点,值等于当前结点的值
Node newNode = new Node(this.value);
//2.把新节点的左子树设置成为当前结点的左子树
newNode.left = this.left;
//3.把新节点的右子树设置成为当前结点的右子结点的左子树
newNode.right = this.right.left;
//4.将当前结点的值设置成为当前结点的右子结点的值
this.value = this.right.value;
//5.将当前结点的右子树设置为当前结点的右子树的右子树
this.right = this.right.right;
//6.将当前结点的左子结点设置为以新节点为根结点的树
this.left = newNode;
}
/**
* 使用右旋转的时机: 添加完一个节点后,就判断:树的左子树的高度 - 右子树的高度是否大于1:
* 如果大于1,则进行右旋转
* 右旋转的方法:
*/
public void rightRotate() {
//1.创建一个新节点newNode,值等于当前结点的值
Node newNode = new Node(this.value);
//2.将新节点的右子树设置成当前结点的右子树
newNode.right = this.right;
//3.将新节点的左子树设置成当前结点的左子结点的右子树
newNode.left = this.left.right;
//4.将当前结点的值更改为当前节点的左子结点的值
this.value = this.left.value;
//5.将当前结点的左子树设置成当前节点的左子结点的左子树
this.left = this.left.left;
//6.将当前结点的右子树设置成以新节点newNode为根节点的树
this.right = newNode;
}
}