Java实现二叉树(二):平衡二叉树的实现之AVL树
前文中,我们实现了二叉查找树,同时,我们也提到了这么一个隐患:如果二叉树无法控制自己的深度,那么,二叉树的查找效率很可能会发生极端的转化–如,顺序的将一堆数据插入查找二叉树,此时,二叉树会成为一个近似链表的数据结构。所以,为了解决这个问题,我们必须要找到问题的根源所在–二叉树深度,由此,推出本文的主角–平衡二叉树。
一、平衡二叉树的概念
定义:平衡二叉树,要求左右子树的深度差别不超过1,且左右子树都是平衡二叉树。(二叉树的定义总是充满了递归味)
平衡二叉树的实现方式:平衡二叉树有多种实现方式,最常见的就有AVL树以及红黑二叉树,AVL树是最早被发明的平衡二叉树,敬它是前辈,本文先说AVL树的实现。
二、AVL树的概念
定义在上头已经说过,咱们直接看看AVL树如何实现自平衡的吧(所谓自平衡,就是在节点发生修改的时候,自己完成树平衡的调整)。
示例:
假设有如下四种树:
要说不平衡的情况其实千变万化,比如左边深度为一万,右边深度为0,但是我们考虑的是,在平衡树中修改一个节点以后,发生的不平衡“事件”,所以,左右的差只能为2。
- 第一种情况,是根节点的左子树高于根节点的右子树,且,左子树的非叶子节点为左子树,此情况称之为“左左”。
解决方案:将左子树的叶子节点–也就是他的右子树,“剪切”到根节点的左边,成为根节点的左叶子,因为他满足大于左子树且小于根节点的要求。再将原左子树变为根节点,根节点变为新右子树,平衡的同时,满足了二叉查找树的要求。
实现步骤: - 第二种情况,对应着图4,属于情况一的对称,就不赘述了。
- 第三种情况,对应图2,同样是,根节点的左子树高于根节点的右子树,但是左子树暴露出来的叶子节点十分不友好,因为他是二叉树的左叶子,此时称之为“左右”,左叶子虽然满足小于根节点的要求,但是不大于左子树,所以无法直接“剪切”。
解决方法:将左子树完成一次逆时针右旋转,再将根节点完成一次顺时针左旋转。
实现步骤: - 第四情况,对应图3,同样对称,也就不说了。
总结:其实旋转的本质,是让不平衡的更深一端,替换根节点,降低其深度,这时,更深端的子树就变成原根节点,如何处理原更深端的子树,就成了最大问题。如左左,右右,都是有一个能符合要求的叶子节点,能直接完成叶子的剪切。左右和右左,暴露出来的叶子都不能完成剪切,所以需要对(左或者右)节点来一次旋转,让上层的树有一个符合要求的叶子。
三、Java实现
不同于普通的二叉查找树,二叉平衡树,需要关注节点的高度,所以需要在数据结构中,加入节点高度的封装。同时,加入左旋转,右旋转,左右旋转,右左旋转的方法,并在删除节点,或者插入节点这类发生节点变动的情况,判断是否树不平衡,以及是哪种情况的不平衡,再处理旋转。Talk is cheap,我们直接看代码吧。
左旋转代码:
//当为“左左”情况的时候,发生的单旋转
public void singleRollLeft()
{
//将临时右子树设置为root
Tree rightTree = root;
//获取root的左子树
Tree leftTree = root.getlChild();
//获取原左子树的右叶子
Tree rightOne = leftTree.getrChild();
//将root置为左子树
root = leftTree;
//将右子树的左节点变为原左子树的右叶子
rightTree.setlChild(rightOne);
root.setrChild(rightTree);
//高度调整,由于只有原来的左子树和原来的父节点发生了变更,所以只修改两个节点的高度
root.getrChild().height = max(root.getrChild().getlChild().height,root.getrChild().getrChild().height)+1;
root.height = max(root.getlChild().height,root.getrChild().height)+1;
}
右旋转代码:
//当为“右右”情况的时候,发生的单旋转
public void singleRollRight()
{
//将临时左子树设置为root
Tree leftTree = root;
//获取root的右子树
Tree rightTree = root.getrChild();
//获取原右子树的左叶子
Tree leftOne = rightTree.getlChild();
//将root置为右子树
root = rightTree;
//将左子树的右节点变为原右子树的左叶子
leftTree.setrChild(leftOne);
root.setlChild(leftTree);
//高度调整
root.getlChild().height = max(root.getlChild().getlChild().height,root.getlChild().getrChild().height)+1;
root.height = max(root.getlChild().height,root.getrChild().height)+1;
}
左右旋转:
//当碰到左右的情况
public void doubleRollLR()
{
TreeUtil leftRoot = new TreeUtil(root.getlChild());
leftRoot.singleRollRight();
singleRollLeft();
}
右左旋转:
//当碰到右左的情况
public void doubleRollRL()
{
TreeUtil rightRoot = new TreeUtil(root.getrChild());
rightRoot.singleRollLeft();
singleRollRight();
}
插入:
//插入
public void insert(int value)
{
if(root == null)
{
root = new Tree();
root.setValue(value);
}
else if(value == root.getValue())
{
return;
}
else if(value > root.getValue()){
TreeUtil rightRoot = new TreeUtil(root.getrChild());
rightRoot.insert(value);
root.setrChild(rightRoot.getRoot());
int leftHeight = 0;
int rightHeight = 0;
if(root.getrChild() != null)
{
rightHeight = root.getrChild().height;
}
if(root.getlChild() != null)
{
leftHeight = root.getlChild().height;
}
if(leftHeight+ 2 == rightHeight )
{
if(value < root.getrChild().getValue())
{
doubleRollRL();
}
else{
singleRollRight();
}
}
}
else
{
TreeUtil leftRoot = new TreeUtil(root.getlChild());
leftRoot.insert(value);
root.setlChild(leftRoot.getRoot());
int leftHeight = 0;
int rightHeight = 0;
if(root.getrChild() != null)
{
rightHeight = root.getrChild().height;
}
if(root.getlChild() != null)
{
leftHeight = root.getlChild().height;
}
if(rightHeight+ 2 == leftHeight )
{
if(value > root.getlChild().getValue())
{
doubleRollLR();
}
else{
singleRollLeft();
}
}
}
}
删除:
//删除
public void delete(int value)
{
if(root == null)
{
return;
}
if(value > root.getValue())
{
TreeUtil rightRoot = new TreeUtil(root.getrChild());
rightRoot.delete(value);
root.setrChild(rightRoot.getRoot());
int leftHeight = 0;
int rightHeight = 0;
if(root.getrChild() != null)
{
rightHeight = root.getrChild().height;
}
if(root.getlChild() != null)
{
leftHeight = root.getlChild().height;
}
if(rightHeight+ 2 == leftHeight )
{
if(value > root.getlChild().getValue())
{
doubleRollLR();
}
else{
singleRollLeft();
}
}
}
else if(value < root.getValue())
{
TreeUtil leftRoot = new TreeUtil(root.getlChild());
leftRoot.delete(value);
root.setlChild(leftRoot.getRoot());
int leftHeight = 0;
int rightHeight = 0;
if(root.getrChild() != null)
{
rightHeight = root.getrChild().height;
}
if(root.getlChild() != null)
{
leftHeight = root.getlChild().height;
}
if(leftHeight+ 2 == rightHeight )
{
if(value < root.getrChild().getValue())
{
doubleRollRL();
}
else{
singleRollRight();
}
}
}
else {
//判断删除节点是否存在左子树
boolean hasLeft = false;
//判断删除节点是否存在右子树
boolean hasRight = false;
if(root.getlChild() != null)
{
hasLeft = true;
}
if(root.getrChild() != null)
{
hasRight = true;
}
if(!hasLeft && !hasRight)//不包含左子树以及右子树
{
root = null;
}
else if(!hasLeft)//只包含右子树
{
root = root.getrChild();
}
else if (!hasRight)
{
root = root.getlChild();
}
else {
Tree rollTree = root.getrChild();
while(rollTree.getlChild() != null)
{
rollTree = rollTree.getlChild();
}
root.setValue(rollTree.getValue());
TreeUtil rightRoot = new TreeUtil(root.getrChild());
rightRoot.delete(rollTree.getValue());
root.setrChild(rightRoot.getRoot());
}
}
}
完整代码git路径:https://github.com/liufangqi/treeTestRealOne
———-
个人再总结:核心理解四种不平衡的状态,左左,右右,左右,右左,对应着“更深”端的位置。左左可以直接单次左旋转完成平衡,右右可以单次右旋转完成平衡,左右,需要将左子树右旋转,再左旋转,右左,需要将右子树左旋转,再右旋转。