平衡二叉树有两种形式:
- 是一棵空树
- 是一个左右两个子树的高度差的绝对值不超过1,并且左右子树都是平衡二叉树的树
平衡二叉树的常用实现方法有AVL树,红黑树(红黑树并不是严格意义上的平衡,并不一定满足任意节点的左右两棵子树高度差不超过1这个条件),替罪羊树,Treap,伸展树等.
在这里我们先谈AVL树,红黑树会在之后的文章再提出.
AVL是最先发明的自平衡二叉查找树算法。在AVL中任何节点的两个儿子子树的高度最大差别为1,所以它也被称为高度平衡树,n个结点的AVL树最大深度约1.44log2n。查找、插入和删除在平均和最坏情况下都是O(log n)。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。
主要包括:
平衡因子是判断树是否平衡的依据,之后的平衡操作都要依据平衡因子来决定是否进行旋转操作和进行怎样的旋转操作.
import java.util.ArrayList;
import java.util.List;
public class AVLTree ,Value> {
/** 根节点 */
private Node root;
/** 内部节点类 */
private class Node{
private Key key;//键
private Value value;//值
private Node left;//左子节点
private Node right;//右子节点
private int N;//以该节点为根节点的子树中的节点总数
private int height=1;//节点的高度
Node(Key key,Value value,int N){
this.key=key;
this.value=value;
this.N=N;
}
}
/**
* 整棵树的节点数量
* @return
*/
public int size(){
return size(root);
}
/**
* 返回以node节点为根的子树的大小(节点数量)
* @param node
* @return
*/
private int size(Node node){
if(node==null){
return 0;
}
return node.N;
}
/**
* 更新节点的size
* @param node
* @return
*/
private int updateSize(Node node){
return size(node.left)+size(node.right)+1;
}
/**
* 更新节点的高度height
* @param node
* @return
*/
private int updateHeight(Node node){
return Math.max(getHeight(node.left),getHeight(node.right))+1;
}
private int getHeight(Node node){
if(node==null){
return 0;
}
return node.height;
}
/**
* 获取节点的平衡因子
* @param node
* @return
*/
private int getBalanceFactor(Node node){
return getHeight(node.left)-getHeight(node.right);
}
主要包括:
/**
* 查找树中key对应的value值
* @param key 要查找的key值
* @return key对应的value值,不存在则返回null
*/
public Value get(Key key){
return get(root,key);
}
/**
* 查找以node节点为根节点的子树中key对应的value值
* @param node 要作为根节点的节点
* @param key 要查找的key值
* @return key对应的value值,不存在则返回null
*/
private Value get(Node node,Key key){
if(node==null){
throw new RuntimeException("Cannot find key "+key);
}
int compareResult = key.compareTo(node.key);
if(compareResult<0){
return get(node.left,key);
}else if(compareResult>0){
return get(node.right,key);
}else{
return node.value;
}
}
/**
* 查找树中最小的key
* @return 最小key值
*/
public Key min(){
return min(root).key;
}
/**
* 查找以node为根节点的子树中最小的key
* @param node 子树的跟节点
* @return 最小的key对应的node
*/
private Node min(Node node){
//如果树为空,则直接返回null
if(node==null){
return null;
}
//如果节点为叶子节点,则返回该节点
if(node.left==null){
return node;
}
//递归查找最左边的节点,也就是最小的节点
return min(node.left);
}
/**
* 查找树中最大的key
* @return 最大key值
*/
public Key max(){
return max(root).key;
}
/**
* 查找以node为根节点的子树中最大的key
* @param node 子树的根节点
* @return 最大的key对应的node
*/
private Node max(Node node){
if(node==null){
return null;
}
if(node.right==null){
return node;
}
return max(node.right);
}
插入操作与二叉查找树(BST)基本一致 , 唯一不同是加了一步node=balance(node);
平衡操作,因为插入节点有可能会破坏树的平衡状态,因此需要进行旋转平衡操作.具体如何进行旋转平衡请查看后面的private Node balance(Node node){...}
方法.
/**
* 插入节点
* @param key 要插入的节点的key
* @param value 要插入的节点的value
*/
public void put(Key key,Value value){
root=put(root,key,value);
}
private Node put(Node node,Key key, Value value){
//node==null则表示已经找到要插入新节点的地方
if(node==null){
return new Node(key,value,1);
}
int compareResult = key.compareTo(node.key);
if(compareResult<0){
node.left=put(node.left,key,value);
}else if(compareResult>0){
node.right=put(node.right,key,value);
}else{
node.value=value;//如果存在相同的key,则更新其value值
}
node.N=updateSize(node);//新增节点需要更新N值
node.height=updateHeight(node);//更新height值
node=balance(node);//平衡操作
return node;
}
删除操作与二叉查找树(BST)也是基本一致,不再赘述,唯一不同的是删除操作同样有可能破坏树的平衡性,因此在删除节点后如果树的平衡性被破坏,需要对树进行平衡操作. 平衡操作同样请查看后面的private Node balance(Node node){...}
方法.
/**
* 删除key对应的节点
* @param key
*/
public void delete(Key key){
root=delete(root,key);
}
private Node delete(Node node,Key key){
//没有查找到
if(node==null){
return null;
}
int compareResult = key.compareTo(node.key);
if(compareResult<0){
//要删除的节点位于左子树
node.left=delete(node.left,key);
}else if(compareResult>0){
//要删除的节点位于右子树
node.right=delete(node.right,key);
}else{
/**
*查找到要删除的节点,分成三种情况
* 1.要删除的节点有左右子树
* 2.要删除的节点有且仅有左子树或者右子树
* 3.要删除的节点为叶子节点,即没有子树
*/
if(node.left!=null&&node.right!=null){
Node minOfRightTree = min(node.right);//node节点中右子树中最小的节点
node.key=minOfRightTree.key;//替换掉要删除节点的key
node.value=minOfRightTree.value;//替换掉要删除节点的value
node.right=delete(node.right,minOfRightTree.key);
}else {
//情况2和情况3均使用子节点替换掉要删除的节点即可.
node=node.left!=null?node.left:node.right;
}
}
//如果删除的不是叶子节点
if(node!=null){
node.N=updateSize(node);
node.height=updateHeight(node);
node=balance(node);//平衡操作
}
return node;
}
破坏树的平衡性的四种情形分别是LL, RR, LR, RL
先来看LL和RR这两种情况(因为LR和RL也是转换成这两种情况然后再进行处理的)
LL需要进行右旋转(即顺时针旋转),来降低树的高度.
RR需要进行左旋转(即逆时针旋转),来降低树的高度.
RR和LL都只需要一次旋转就可以实现平衡.而LR则需要先进行左旋转来转化为LL情形,然后再如LL情形一样进行右旋转,总共两次旋转来实现平衡.
转化为LL的情形:
RR和LL都只需要一次旋转就可以实现平衡.而RL则需要先进行右旋转来转化为RR情形,然后再如RR情形一样进行左旋转,总共两次旋转来实现平衡.
转化为RR的情形:
private Node balance(Node node)方法
private Node balance(Node node){
if(Math.abs(getBalanceFactor(node))>1){
//LL情况,对根节点使用右旋转,如果getBalanceFactor(node.left)大于等于0修改为大于0,则等于的情况需要的LR情况下处理,增加了不必要的左旋转操作。RR情况同理
if((getBalanceFactor(node)>1)&&(getBalanceFactor(node.left)>=0)){
node=rightRotate(node);
}
//RR情况,对根节点使用左旋转
else if((getBalanceFactor(node)<-1)&&(getBalanceFactor(node.right)<=0)){
node=leftRotate(node);
}
//LR情况,先对根节点的左子树使用左旋转,后对根节点使用右旋转
else if((getBalanceFactor(node)>1)&&(getBalanceFactor(node.left)<0)){
node.left=leftRotate(node.left);
node=rightRotate(node);
}
//RL情况,先是对根节点的右子树使用右旋转,后对根节点使用左旋转
else{
node.right=rightRotate(node.right);
node=leftRotate(node);
}
}
return node;
}
/**
* 右旋转
* @param x
* @return
*/
private Node rightRotate(Node x){
Node y=x.left;
Node T3=y.right;
x.left=T3;
y.right=x;
//更新height
x.height=updateHeight(x);
y.height=updateHeight(y);
x.N=updateSize(x);
y.N=updateSize(y);
return y;
}
/**
* 左旋转
* @param x
* @return
*/
private Node leftRotate(Node x){
Node y=x.right;
Node T3=y.left;
x.right=T3;
y.left=x;
//更新height
x.height=updateHeight(x);
y.height=updateHeight(y);
x.N=updateSize(x);
y.N=updateSize(y);
return y;
}
public List keys(){
ArrayList list = new ArrayList<>();
keys(root,list);
return list;
}
private void keys(Node node, ArrayList list){
if(node==null){
return;
}
keys(node.left,list);
list.add(node.key);
keys(node.right,list);
}
}
浅析树结构(一)二叉查找树(BST树代码实现)
浅析树结构(二)AVL平衡二叉树(AVL树原理及代码实现)
浅析树结构(三)红黑树