数据结构之二叉树(十):平衡二叉树(AVL树)的创建和使用

平衡二叉树(AVL树)的创建和使用

引言:
前一篇我们讲过了二叉排序树的创建和使用。当给出一个数组[1,2,3,4,5,6]
时我们创建一个二叉排序树
如下:
在这里插入图片描述
存在问题:
左子树为空,对插入速度没有影响。
查询速度明显变慢,因为每次还需要比较左子树,其查询速度比链表还要慢。
为解决这种问题,我们本篇讲讲平衡二叉树(AVL树)

基本介绍:
平衡二叉树是在二叉排序树的基础上实现的。
1)平衡二叉树也叫平衡二叉搜索树,又成为AVL树,可以保证查询效率较高。
2)具有一下特点:它是一棵空树或者它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树,平衡二叉树的常用实现方法有红黑树,AVL,替罪羊树,Treap,伸展树等。
平衡二叉搜索树:
在这里插入图片描述
非平衡二叉搜索树:
在这里插入图片描述
平衡二叉树构建的思路解析:
1)给出数组[4,3,6,5,7,8],首先将其构建成一棵二叉排序树,如下
在这里插入图片描述

2)很显然我们发现根节点左子树高度为1,右子树高度为3,不是平衡二叉树。右子树高度较高,我们此时需要将其左旋转,构建成一棵平衡二叉树。
2.1)首先,我们创建一个新的节点,值等于当前根节点的值,把新节点的左子树设置为当前根节点的左子树。
在这里插入图片描述
2.2)把新节点的右子树设置为当前根节点的右子树的左子树
在这里插入图片描述
2.3)把当前根节点的值换为右子节点的值
在这里插入图片描述
2.4)把当前根节点的右子树设置为右子树的右子树
在这里插入图片描述
2.5)把当前根节点的左子树设置为新节点
在这里插入图片描述
整理一下便是:
在这里插入图片描述
此时,便是一棵平衡二叉树。

代码构建:
首先我们得树节点类可以复制前篇的二叉排序树的Node节点类

在之中我们加入了三个方法,分别是
以当前节点为根节点的树的高度

//返回当前节点的高度,以该节点为根节点的树的高度
	public int height() {
    
    
		return Math.max(left==null?0:left.height(),right==null?0:right.height())+1;
	}

当前节点左子树的高度

//返回左子树的高度
	public int leftHeight() {
    
    
		if(left==null) {
    
    
			return 0;
		}
		return left.height();
	}

当前节点右子树的高度

//返回左子树的右高度
	public int rightHeight() {
    
    
		if(right==null) {
    
    
			return 0;
		}
		return right.height();
	}

接下来进行左旋转方法的实现:

//左旋转方法
	private void leftRotate() {
    
    
		//创建新的节点,以当前根节点的值
		Node newNode=new Node(value);
		//把新的节点的左子树设置成当前节点的左子树
		newNode.left=left;
		//把新的节点的右子树设置成当前节点的右子树的左子树
		newNode.right=right.left;
		//把当前节点的值替换成右子树的值
		value=right.value;
		//把当前节点的右子树设置成右子树的右子树
		right=right.right;
		//把当前节点的左子节点设置成新的节点
		left=newNode;
	}

左旋转方法的调用是在每次添加一个新的节点时都要检测根节点两边左右高度是否在1的高度差之内,如果右子树比左子树的高度差大于1,则进行左旋转。
add方法实现:

//添加节点的方法
	//递归的形式添加节点
	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) {
    
    
			leftRotate();//左旋转..
		}
	}

右旋转思路解析:

给出数组[10,12,8,9,7,6},将其构建一棵二叉排序树,如图:
在这里插入图片描述
很显然发现根节点左子树高度为3,右子树高度为1,此时进行右旋。
右旋转与左旋转思路大致一致
如下:
1)创建一个新的节点newNode,值等于当前根节点的值
2)把新节点的右子树设置为当前根节点的右子树
3)把当前节点的值换为左子节点的值
4)把当前节点的左子树设置成左子树的左子树
5)把当前节点的右子树设置成新节点
经过整理后就变成了如下:(过程大致与左子树一致,不再详述。
在这里插入图片描述
此时8节点变成了根节点,左右子树的高度均为2.
右旋转方法代码:

//右旋转方法
	private void rightRotate() {
    
    
		//创建新的节点,以当前根节点的值
		Node newNode = new Node(value);
		newNode.right=right;
		newNode.left=left.right;
		value=left.value;
		left=left.left;  
		right=newNode;
	}

在add方法中添加

//添加节点的方法
	//递归的形式添加节点
	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) {
    
    
			leftRotate();//左旋转..
		}
		//当添加完一个节点后,(如果左子树的高度-右子树的高度)>1,右旋转
		if(leftHeight()-leftHeight()>1) {
    
    
			rightRotate();
		}
	}

双旋转思路解析

前面的两个数列,进行单旋转(即一次旋转)就可以将非平衡二叉树转成平衡二叉树,但在某些情况下,单旋转不能完成平衡二叉树的转换。
比如[10,11,7,6,8,9];
构建成二叉排序树如下:
在这里插入图片描述
将其右旋转后得:
在这里插入图片描述
可发现左子树高度变成了1,右子树高度变成了3,并没有变成平衡二叉树。
问题分析:
1.当符合右旋转的条件:
2.如果它得左子树的右子树高度大于它的左子树的高度
3.先对当前这个节点的左节点进行左旋转
4.再对当前节点进行右旋转的操作即可
这种情况只需在add方法里修改一下即可:

//添加节点的方法
	//递归的形式添加节点
	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) {
    
    
			//如果它的右子树的左子树高度大于它的右子树的右子树高度
			if(right!=null&&right.leftHeight()>right.rightHeight()) {
    
    
				//先对当前节点的左节点(左子树)进行左旋转
				right.rightRotate();
				//再对当前节点进行右旋转
				leftRotate();
			}else {
    
    
				//直接进行右旋转
				leftRotate();
			}
			return;//必须要
		}
		//当添加完一个节点后,(如果左子树的高度-右子树的高度)>1,右旋转
		if(leftHeight()-leftHeight()>1) {
    
    
			//如果它的左子树的右子树高度大于它的左子树的左子树高度
			if(left!=null&&left.rightHeight()>left.leftHeight()) {
    
    
				//先对当前节点的左节点(左子树)进行左旋转
				left.leftRotate();
				//再对当前节点进行右旋转
				rightRotate();
			}else {
    
    
				//直接进行右旋转
				rightRotate();
			}
		}
	}

全部代码:

在这里插入代码片package Tree08;

public class AVLTreeDemo {
    
    
	public static void main(String[] args) {
    
    
		int[] arr= {
    
    4,3,6,5,7,8};
		//创建一个AVLTree对象
		AVLTree avlTree=new AVLTree();
		//添加节点
		for(int i=0;i<arr.length;i++) {
    
    
			avlTree.add(new Node(arr[i]));
		}
		System.out.println("中序遍历");
		avlTree.middleShow();
	}
}
//创建平衡二叉树
class AVLTree{
    
    
	private Node root;
	//删除节点
	public void delNode(int value) {
    
    
		if(root==null) {
    
    
			return;
		}else {
    
    
			//首先找到要删除节点 targetNode
			Node targetNode=search(value);
			//可能找不到节点
			if(targetNode==null) {
    
    
				return;
			}
			//如果我们发现当前这颗二叉树只有一个节点
			if(root.left==null&&root.right==null) {
    
    
				root=null;
				return;
			}
			//去找targetNode的父节点
			Node parent=searchParent(value);
			//如果删除的节点是叶子节点
			if(targetNode.left==null&&targetNode.right==null) {
    
    
				//判断targetNode是父节点的左子节点,还是右子节点
				if(parent.left!=null&&parent.left.value==value) {
    
    
					parent.left=null;
	 			}else if(parent.right!=null &&parent.right.value==value) {
    
    
					parent.right=null;
				}
			}else if(targetNode.left!=null&&targetNode.right!=null) {
    
    
				int minVal=delRightTreeMin(targetNode.right);
				targetNode.value=minVal;
			}else {
    
    //删除只有一棵子树的节点
				//如果删除的节点有左子节点
				if(targetNode.left!=null) {
    
    
					//如果tatgetNode是parent的左子节点
					if(parent.left.value==value) {
    
    
						parent.left=targetNode.left;
					}else {
    
    //targetNode是parent的右子节点
						parent.right=targetNode.left;
					}
				}else {
    
    //删除的节点有右子节点
					//如果tatgetNode是parent的左子节点
					if(parent.left.value==value) {
    
    
						parent.left=targetNode.right;
					}else {
    
    //targetNode是parent的右子节点
						parent.right=targetNode.right; 
					}
				}
			}
		}
	}
	//返回以node为根节点的二叉排序树的最小节点的值
	//删除node为根节点的二叉排序树的最小节点
	public int delRightTreeMin(Node node) {
    
    
		Node target=node;
		//循环查找左节点,就会找到最小值
		while(target.left!=null) {
    
    
			target=target.left;
		}
		//这时target指向了最小节点
		//删除最小节点
		delNode(target.value);
		return target.value;
	}
	//查找以node根节点的二叉排序树的最小节点的
	//查找要删除节点
	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);
		}
	}
	
	//添加节点的方法
	public void add(Node node) {
    
    
		if(root==null) {
    
    
			root=node;//如果root为空则直接让node作为root
		}else {
    
    
			root.add(node);
		}
	}
	//中序遍历
	public void middleShow() {
    
    
		if(root!=null) {
    
    
			root.middleShow();
		}else {
    
    
			System.out.println("树为空,不能遍历");
		}
	}
}
//创建Node节点
class Node{
    
    
	int value;
	Node left;
	Node right;
	public Node(int value) {
    
    
		this.value=value;
	}
	//左旋转方法
	private void leftRotate() {
    
    
		//创建新的节点,以当前根节点的值
		Node newNode=new Node(value);
		//把新的节点的左子树设置成当前节点的左子树
		newNode.left=left;
		//把新的节点的右子树设置成当前节点的右子树的左子树
		newNode.right=right.left;
		//把当前节点的值替换成右子树的值
		value=right.value;
		//把当前节点的右子树设置成右子树的右子树
		right=right.right;
		//把当前节点的左子节点设置成新的节点
		left=newNode;
	}
	//右旋转方法
	private void rightRotate() {
    
    
		//创建新的节点,以当前根节点的值
		Node newNode = new Node(value);
		newNode.right=right;
		newNode.left=left.right;
		value=left.value;
		left=left.left;  
		right=newNode;
	}
	//返回左子树的高度
	public int leftHeight() {
    
    
		if(left==null) {
    
    
			return 0;
		}
		return left.height();
	}
	//返回左子树的右高度
	public int rightHeight() {
    
    
		if(right==null) {
    
    
			return 0;
		}
		return right.height();
	}
	//返回当前节点的高度,以该节点为根节点的树的高度
	public int height() {
    
    
		return Math.max(left==null?0:left.height(),right==null?0:right.height())+1;
	}
	//查找要删除的节点
	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);
		}
	}
	//查找要删除节点的父节点
	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 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) {
    
    
			//如果它的右子树的左子树高度大于它的右子树的右子树高度
			if(right!=null&&right.leftHeight()>right.rightHeight()) {
    
    
				//先对当前节点的左节点(左子树)进行左旋转
				right.rightRotate();
				//再对当前节点进行右旋转
				leftRotate();
			}else {
    
    
				//直接进行右旋转
				leftRotate();
			}
			return;//必须要
		}
		//当添加完一个节点后,(如果左子树的高度-右子树的高度)>1,右旋转
		if(leftHeight()-leftHeight()>1) {
    
    
			//如果它的左子树的右子树高度大于它的左子树的左子树高度
			if(left!=null&&left.rightHeight()>left.leftHeight()) {
    
    
				//先对当前节点的左节点(左子树)进行左旋转
				left.leftRotate();
				//再对当前节点进行右旋转
				rightRotate();
			}else {
    
    
				//直接进行右旋转
				rightRotate();
			}
		}
	}
	//中序遍历
	public void middleShow() {
    
    
		if(this.left!=null) {
    
    
			this.left.middleShow();
		}
		System.out.print(this.value+" ");
		if(this.right!=null) {
    
    
			this.right.middleShow();
		}
	}
}

猜你喜欢

转载自blog.csdn.net/qq_45273552/article/details/109255455
今日推荐