一、平衡二叉树
平衡二叉树又叫平衡二叉搜索树(Self-balancing Binary Search Tree),又被称为AVL树。
平衡二叉树可定义为或者是一棵空树,或者是具有下列性质的二叉树:其左子树和右子树均为平衡二叉树,且左子树和右子树的高度差的绝对值不超过1。注意:平衡二叉树一定是二叉排序树。含有n个结点的平衡二叉树的最大深度为O(log2(n)),即平衡二叉树的平均查找长度为O(log2(n))。
如下图所示为一棵平衡二叉树和一棵非平衡二叉树:
二、平衡二叉树的插入
平衡二叉树的插入过程的前半部分与二叉排序树相同,但在新结点插入后,若造成查找路径上的某个结点不再平衡,则需要做出相应的调整。可将调整的规律归纳为下列四种情况:
(1)RR平衡旋转(左单旋转)
前提条件:右子树的高度减去左子树的高度大于1,即:rightHeight() - leftHeight() > 1。
(1)创建一个新的结点newNode,使其值等于当前根结点的值;
(2)将新结点的左子树设置为当前结点的左子树:newNode.left=left;
(3)将新结点的右子树设置为当前结点的右子树的左子树:newNode.right =right.left;
(4)将当前结点的值换为右子结点的值:value=right.value;
(5)将当前结点的右子树设置成右子树的右子树:right=right.right;
(6)将当前结点的左子树设置成新结点:left=newNode。
示意图:
private void leftRotate() {
Node newNode =new Node(value);//创建一个新的结点newNode
newNode.left=left;//将新结点的左子树设置为当前结点的左子树
newNode.right=right.left;//将新结点的右子树设置为当前结点的右子树的左子树
value=right.value;//将当前结点的值换为右子结点的值
right=right.right;//将当前结点的右子树设置成右子树的右子树
left=newNode;//将当前结点的左子树设置成新结点
}
(2)LL平衡旋转(右单旋转)
前提条件:左子树的高度减去右子树的高度大于1,即:leftHeight() - rightHeight() > 1。
(1)创建一个新的结点newNode,使其值等于当前根结点的值;
(2)将新结点的右子树设置为当前结点的右子树:newNode.right=right;
(3)将新结点的左子树设置为当前结点的左子树的右子树:newNode.left =left.right;
(4)将当前结点的值换为左子结点的值:value=left.value;
(5)将当前结点的左子树设置成左子树的左子树:left=left.left;
(6)将当前结点的右子树设置成新结点:right=newNode。
示意图:
private void rightRotate() {
Node newNode =new Node(value);//创建一个新的结点newNode
newNode.right=right;//将新结点的右子树设置为当前结点的右子树
newNode.left=left.right;//将新结点的左子树设置为当前结点的左子树的右子树
value=left.value;//将当前结点的值换为左子结点的值
left=left.left;//将当前结点的左子树设置成左子树的左子树
right=newNode;//将当前结点的右子树设置成新结点
}
(3)LR平衡旋转(先左后右双旋转)
前提条件:符合RR平衡旋转(左单旋转)前提下,当前结点的左子树的右子树高度大于它的左子树的左子树的高度,即:left.rightHeight()>left.leftHeight()。
(1)先对当前结点的左结点进行左单旋转;
(2)再对当前结点进行右单旋转即可。
if(leftHeight()-rightHeight()>1) {//当添加完一个结点后,如果 :(左子树的高度 - 右子树的高度) > 1, 右旋转
if(left!=null&&left.rightHeight()>left.leftHeight()) {//若它的左子树的右子树高度大于它的左子树的左子树的高度
left.leftRotate();//LR平衡旋转(先左后右双旋转)
rightRotate();
}else {
rightRotate();//LL平衡旋转(右单旋转)
}
}
(4)RL平衡旋转(先右后左双旋转)
前提条件:符合LL平衡旋转(右单旋转)前提下,当前结点的右子树的左子树的高度大于它的右子树的右子树的高度,即: right.leftHeight()>right.rightHeight()。
(1)先对当前结点的右结点进行右单旋转;
(2)再对当前结点进行左单旋转即可。
if(rightHeight()-leftHeight()>1) {//当添加完一个结点后,如果: (右子树的高度-左子树的高度) > 1 ,左旋转
if(right!=null&&right.leftHeight()>right.rightHeight()) {//若它的右子树的左子树的高度大于它的右子树的右子树的高度
right.rightRotate();//RL平衡旋转(先右后左双旋转)
leftRotate();
}else {
leftRotate();//RR平衡旋转(左单旋转)
}
return;
}
三、实现完整代码
package Tree;
public class AVLTree {
public static void main(String[] args) {
int[] arr= {10,11,7,6,8,9};
AVL avlTree=new AVL();
for(int i=0;i<arr.length;i++) {
avlTree.add(new Node(arr[i]));
}
System.out.println("初始平衡二叉树的中序遍历");
avlTree.inOrder();
System.out.println("双旋转处理后:");
avlTree.inOrder();
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());// 8
System.out.println("根节点的左结点=" + avlTree.getRoot().left);// 7
System.out.println("根节点的右结点=" + avlTree.getRoot().right);// 10
}
}
class AVL{
private Node root;
public Node getRoot() {//获取根结点
return root;
}
public void add(Node node) {//添加子结点
if (root == null) {//若根结点为空直接让添加结点成为子结点
root = node;
} else {
root.add(node);
}
}
public void inOrder() {//中序遍历
if(root!=null) {//若根结点不为空则调用结点的inOrder
root.inOrder();
}else {
System.out.println("平衡二叉树为空,无法遍历!");
}
}
}
class Node{
int value;
Node left;
Node right;
public Node(int value) {//Node的构造函数
this.value=value;
}
@Override
public String toString() {//重写toString方法
return "Node [value=" + value + "]";
}
public int height() {//返回以该结点为根结点的树的高度
return Math.max(left==null ? 0:left.height(), right==null ? 0:right.height())+1;
}
public int leftHeight() {//返回左子树的高度
if(left==null) {//左子树为空直接返回0
return 0;
}
return left.height();//递归左子树的高度
}
public int rightHeight() {//返回右子树的高度
if(right==null) {//右子树为空直接返回0
return 0;
}
return right.height();//递归右子树的高度
}
//RR平衡旋转(左单旋转)
private void leftRotate() {
Node newNode =new Node(value);//创建一个新的结点newNode
newNode.left=left;//将新结点的左子树设置为当前结点的左子树
newNode.right=right.left;//将新结点的右子树设置为当前结点的右子树的左子树
value=right.value;//将当前结点的值换为右子结点的值
right=right.right;//将当前结点的右子树设置成右子树的右子树
left=newNode;//将当前结点的左子树设置成新结点
}
//LL平衡旋转(右单旋转)
private void rightRotate() {
Node newNode =new Node(value);//创建一个新的结点newNode
newNode.right=right;//将新结点的右子树设置为当前结点的右子树
newNode.left=left.right;//将新结点的左子树设置为当前结点的左子树的右子树
value=left.value;//将当前结点的值换为左子结点的值
left=left.left;//将当前结点的左子树设置成左子树的左子树
right=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);//否则递归向当前结点的右子树遍历
}
}
if(rightHeight()-leftHeight()>1) {//当添加完一个结点后,如果: (右子树的高度-左子树的高度) > 1 ,左旋转
if(right!=null&&right.leftHeight()>right.rightHeight()) {//若它的右子树的左子树的高度大于它的右子树的右子树的高度
right.rightRotate();//RL平衡旋转(先右后左双旋转)
leftRotate();
}else {
leftRotate();//RR平衡旋转(左单旋转)
}
return;
}
if(leftHeight()-rightHeight()>1) {//当添加完一个结点后,如果 :(左子树的高度 - 右子树的高度) > 1, 右旋转
if(left!=null&&left.rightHeight()>left.leftHeight()) {//若它的左子树的右子树高度大于它的左子树的左子树的高度
left.leftRotate();//LR平衡旋转(先左后右双旋转)
rightRotate();
}else {
rightRotate();//LL平衡旋转(右单旋转)
}
}
}
public void inOrder() {//中序遍历
if(this.left!=null) {
this.left.inOrder();
}
System.out.println(this);
if(this.right!=null) {
this.right.inOrder();
}
}
}
运行结果:
初始平衡二叉树的中序遍历
Node [value=6]
Node [value=7]
Node [value=8]
Node [value=9]
Node [value=10]
Node [value=11]
双旋转处理后:
Node [value=6]
Node [value=7]
Node [value=8]
Node [value=9]
Node [value=10]
Node [value=11]
树的高度=3
树的左子树高度=2
树的右子树高度=2
当前的根结点=Node [value=8]
根节点的左结点=Node [value=7]
根节点的右结点=Node [value=10]
该实例的变化过程如下图所示: