平衡二叉树
提出背景
给定一个数列[1,2,3,4,5,6],要求创建一颗二叉【排序树(BST)
存在的问题:
- 左子树全部为空,从形式上看,更像一个单链表
- 插入速度没有影响
- 查询的速度明显降低(因为需要依次比较),不能发挥BST的优势,因为每次还需要比较左子树,其查询速度比单链表还慢
- 解决方案----平衡二叉树(AVL)
基本介绍
- 平衡二叉树也叫平衡二叉搜索树(排序树) 又被称为AVL树可以保证保证查询效率较高
- 具有以下特点:它是一颗空树或它的左右两个子树的高度差的绝对值不超过一,并且左右两个子树都是一颗平衡二叉树
- 平衡二叉树树的实现方法有:红黑树,AVL树,替罪羊树,Treap、伸展树
左旋转
分析
右子树高度较高,进行左旋转,降低右子树的高度
- 创建一个新的节点 newNode,值等于当前根节点的值
- 把新节点的的左子树设置为当前节点的左子树,newNode.left = left
- 把新节点的右子树设置为当前节点的右子树的左子树 newNode.right = right.left
- 把当前节点的值置换成右子节点的值 value = righty.value
- 把当前节点的右子树设置成右子树的右子树 right = right.value
- 把当前节点的左子树设置为新节点 left = newNode
代码
//左旋转方法
public void leftRoate(){
//创建新的节点,以当前跟节点的值
Node newNode = new Node(value);
//把新的节点的左子树设置为当前节点的左子树
newNode.left = this.left;
//把新节点的右子树设置为当前节点右子树的左子树
newNode.right = this.right.left;
//把当前节点的值替换成右子节点的值
value = right.value;
//把当前节点的右子树设置为当前节点的右子树的右子树
right = right.right;
//把当前节点的左子树设置为新的节点
this.left = newNode;
}
public void add (Node node){
if(node==null){
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(rightHeight()-leftHeight()>1){
leftRoate(); //左旋转
}
}
右旋转
分析
左子树高度较高,进行右旋转,降低左子树的高度
- 创建一个新的节点 newNode,值等于当前根节点的值
- 把新节点的右子树设置为当前节点的右子树 newNode.right = right
- 把新节点的左子树设置为当前节点的左子树的右子树 newNode.left = left.right
- 把当前节点的值转换为左子节点的值
- 把当前节点的左子树设置成左子树的左子树 left = left.left
- 把当前节点的右子树设置成新的节点 right=newNode
代码
//有旋转
public void rightRoate(){
Node newNode = new Node(value);
//把新的节点的you 子树设置为当前节点的右子树
newNode.right = this.right;
//把新节点的左子树设置为当前节点左子树的右子树
newNode.left = this.left.right;
//把当前节点的值替换成左子节点的值
value = left.value;
//把当前节点的左子树设置为当前节点的左子树的左子树
left = left.left;
//把当前节点的you子树设置为新的节点
this.right = newNode;
}
双旋转
提出背景
前面的两个数列,进行单旋转(即一次旋转)就可以将非平衡二叉树转成平衡二叉树,但是在某些情况下,单旋转不能完成平衡二叉树的转换。例如数列:10 11 7 6 8 9
问题分析**
-
当符合右旋转条件时
-
如果 左子树的的右子树的高度 大于 它的左子树的高度
-
先对当前这个节点的左节点(节点7)进行左旋转
-
再对当前节点(节点10)进行右旋转
代码
//当添加完一个节点后,如果:右子树高度-左子树的高度>1,左旋转 if(rightHeight()-leftHeight()>1){ //如果他的you子树的左子树的高度大于它右子树高度 if(right!=null&&right.leftHeight()>right.rightHeight()){ //先要对当前节点的you子树 进行右旋转 right.rightRoate(); //再对当前节点进行zuo旋转 leftRoate(); }else{ leftRoate(); //左旋转 } return; } //当添加完一个节点后,如果:左子树高度-右子树的高度>1,右旋转 if(leftHeight()-rightHeight()>1){ //如果他的左子树的右子树的高度大于它左子树高度 if(left!=null&&left.rightHeight()>left.leftHeight()){ //先要对当前节点的左子树 进行左旋转 left.leftRoate(); //再对当前节点进行右旋转 rightRoate(); }else{ rightRoate(); //右旋转 } }
代码
package J树的提高.avl;
/**
* @Author Zhou jian
* @Date 2020 ${month} 2020/1/13 0013 12:22
*/
public class AVLTreeDemo {
public static void main(String[] args) {
// int[] arr = {4,3,6,5,7,8};
int[] arr = {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("中序遍历");
avlTree.infixOrder();
// System.out.println("在没有旋转之前");
// System.out.println("树的高度"+avlTree.getRoot().height());
// System.out.println("树的左子树高度"+avlTree.getRoot().leftHeight());
// System.out.println("树的右子树高度"+avlTree.getRoot().rightHeight());
System.out.println("在旋转之后");
System.out.println("树的高度"+avlTree.getRoot().height());
System.out.println("树的左子树高度"+avlTree.getRoot().leftHeight());
System.out.println("树的右子树高度"+avlTree.getRoot().rightHeight());
//因为右子树高度大于左子树高度,所以需要左旋转
}
}
//创建AVLTree
//创建二叉排序树
class AVLTree{
//头结点
private Node root;
public Node getRoot() {
return root;
}
public void setRoot(Node root) {
this.root = root;
}
//添加节点的方法
public void add(Node node){
//如果root为空,则直接赋值root怪
if(root==null){
root = node;
}else{
root.add(node);
}
}
//中序遍历
public void infixOrder(){
if(root!=null){
root.infixOrder();
}else{
System.out.println("当前二叉排序树为空不能遍历");
}
}
//查找节点
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);
}
}
/**
* @param node 待传入的节点(当做一颗排序树的根节点)
* @return 以node为跟节点的二叉排序树最小节点的值
* //1、返回以node为跟节点的二叉排序树最小节点的值
* //2、删除 以node为跟节点的二叉排序树最小节点的值
* */
public int deleteRightMin(Node node){
Node temp= node;
//循环的查找左节点,直到找到最小值
while(temp.left!=null){
temp = temp.left;
}
//这时target就指向了最小的值
//删除最小节点
deleteNode(temp.value);
return temp.value;
}
//删除节点
public void deleteNode(int value){
if(this.root==null){
return;
}else{
//1、先根据需求查找要删除的节点 targetNode
Node targetNode = Search(value);
//如果没有找到要删除的节点
if(targetNode==null){
return;
}
//如果发现当前二叉排序树只有一个节点
if(root.left==null && root.right==null){
root = null;
}
//去查找targetNode的父节点
Node parent = searchParent(value);
//如果要删除的节点是叶子节点
if(targetNode.left==null&&targetNode.right==null){
//判断targetNode是parentNode的左子节点还是右子节点
if(parent.left!=null&&parent.left==targetNode){
parent.left=null;
}
if(parent.right!=null&&parent.right==targetNode){
parent.right=null;
}
//要删除的节点有两个子节点
}else if(targetNode.left!=null&&targetNode.right!=null){
//从右子树找最小的 取待待删除的节点
int minValue = deleteRightMin(targetNode.right);
targetNode.value = minValue;
}else{//要删除的节点有一个子节点
//要删除的节点有一个右子节点
if(targetNode.right!=null){
//要删除的节点是parent的左子节点
if( parent.left==targetNode){
parent.left = targetNode.right;
//要删除的节点是parent的右子节点
}else if( parent.right==targetNode){
parent.right=targetNode.right;
}
}else{//要删除的节点有一个左子节点
//要删除的节点是parent的左子节点
if(parent.left==targetNode){
parent.left = targetNode.left;
//要删除的节点是parent的右子节点
}else if(parent.right==targetNode){
parent.right=targetNode.left;
}
}
}
}
}
}
//创建Node节点
class Node{
int value;
Node left;
Node right;
public Node(int value) {
this.value = value;
}
//返回左子树的高度
public int leftHeight(){
if(left==null){
return 0;
}else{
return left.height();
}
}
//返回右子树的高度
public int rightHeight(){
if(right==null){
return 0;
}else{
return right.height();
}
}
//返回当前节点为根节点树的高度
public int height(){
return Math.max(left==null?0:left.height(),right==null?0:right.height())+1;
}
@Override
public String toString() {
return "Node{" +
"value=" + value +
'}';
}
//添加节点的方法
//递归的形式添加节点,注意需要满足二叉排序树的要求
public void add (Node node){
if(node==null){
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(rightHeight()-leftHeight()>1){
//如果他的you子树的左子树的高度大于它右子树高度
if(right!=null&&right.leftHeight()>right.rightHeight()){
//先要对当前节点的you子树 进行右旋转
right.rightRoate();
//再对当前节点进行zuo旋转
leftRoate();
}else{
leftRoate(); //左旋转
}
return;
}
//当添加完一个节点后,如果:左子树高度-右子树的高度>1,右旋转
if(leftHeight()-rightHeight()>1){
//如果他的左子树的右子树的高度大于它左子树高度
if(left!=null&&left.rightHeight()>left.leftHeight()){
//先要对当前节点的左子树 进行左旋转
left.leftRoate();
//再对当前节点进行右旋转
rightRoate();
}else{
rightRoate(); //右旋转
}
}
}
//中序遍历
public void infixOrder(){
if(this.left!=null){
this.left.infixOrder();
}
System.out.println(this);
if(this.right!=null){
this.right.infixOrder();
}
}
//查找要删除的节点
/**
* @param value 希望删除节点的值
*/
public Node Search(int value){
if(value==this.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);
}
}
//查找要删除节点的父亲节点
/**
*
* @param value
* @return 返回的是要删除节点的父节点
*/
public Node searchParent(int value){
//如果当前节点就是要删除节点的父节点就返回
if(((this.left!=null) &(this.left.value==value))||((this.right!=null) &(this.right.value==value))){
return this;
}else {
//如果查找的值小于当前节点的值并且当前节点的左子节点不为空
//递归向左子节点删除
if((this.left!=null)&&(value<this.value)){
return this.left.searchParent(value);
} else if((this.right!=null)&&(value>this.value)){//向右子树递归查找
return this.right.searchParent(value);
}else{
return null;//没有找到父节点
}
}
}
//左旋转方法
public void leftRoate(){
//创建新的节点,以当前跟节点的值
Node newNode = new Node(value);
//把新的节点的左子树设置为当前节点的左子树
newNode.left = this.left;
//把新节点的右子树设置为当前节点右子树的左子树
newNode.right = this.right.left;
//把当前节点的值替换成右子节点的值
value = right.value;
//把当前节点的右子树设置为当前节点的右子树的右子树
right = right.right;
//把当前节点的左子树设置为新的节点
this.left = newNode;
}
//有旋转
public void rightRoate(){
Node newNode = new Node(value);
//把新的节点的you 子树设置为当前节点的右子树
newNode.right = this.right;
//把新节点的左子树设置为当前节点左子树的右子树
newNode.left = this.left.right;
//把当前节点的值替换成左子节点的值
value = left.value;
//把当前节点的左子树设置为当前节点的左子树的左子树
left = left.left;
//把当前节点的you子树设置为新的节点
this.right = newNode;
}
}