在学习了普通二叉排序树的算法之后,我们发现了它的一些不足,如果我们的普通二叉树在插入数据的时候造成树的一侧数据过多的话,就会使得查找的时候效率大大降低,在极端情况下,我们插入一串有序的数,原本的二叉树就会变成链表,例如,插入:1,2,3,4,5,6,结果就会变成下图这样:
这样就违背了我们二叉树的初衷,相当于有一边的孩子节点失去了意义,这种二叉树就是不平衡的二叉树。
那么具体什么样的二叉树是不平衡的二叉树呢?
我们规定,如果一个树节点的左孩子节点高度和右孩子节点高度的差大于等于2,则这个节点就是不平衡的节点,同时这个差值叫做这个节点的平衡因子。
也就是说,如果一个节点的平衡因子绝对值大于等于2了,这个节点就失衡了。
例如下面几种情况(这里平衡因子以左子树高度-右子树高度为例):
1.左单旋操作
我们发现,右子树比左子树高度差达到2了,形象地来说,现在右边的树枝太重了,树不平衡了。如果变成下图这样就会平衡了:
这里,我们称为对结点1做了左单旋操作。
2.右单旋操作
我们发现,左子树比右子树高度差达到2了,树不平衡了。如果变成下图这样就会平衡了:
这里,我们称为对结点3做了右单旋操作。
3.先右旋再左旋
对于这种情况,我们需要通过两步将它变为平衡:
1.先对2节点进行右旋
2.再对1节点进行左旋
4.先左旋再右旋
1.先对1节点进行左旋
2.再对2节点进行右旋
至此,我们已经说完了各种基本操作,现在让我们来看代码:
树的结构:
public class Tree {
public Tree leftChild;
public Tree rightChild;
public int data;//记录节点的数据
public int height;//用来记录节点的高度
}
基本操作:
public static Tree rightRotate(Tree root)//右单旋
{ // root为失衡的树点,也就是平衡因子的绝对值大于等于2的点
Tree temp = root.leftChild;
root.leftChild = temp.rightChild; // 将失衡点root的左孩子temp的右子树成为失衡点p的左子树
temp.rightChild = root; // 将失衡点作为temp的右子树
// 重新调整失衡点及其左孩子节点的高度值(只有这两个节点的高度值可能发生改变)
root.height = Math.max(getHeight(root.leftChild), getHeight(root.rightChild)) + 1;
temp.height = Math.max(getHeight(temp.leftChild), root.height) + 1;
return temp; // 失衡点root的左孩子成为新的根节点(取代原失衡点的位置)
}
public static Tree leftRotate(Tree root)//左单旋
{
Tree temp = root.rightChild;
root.rightChild = temp.leftChild; // 将失衡点root的右孩子temp的左子树成为失衡点root的右子树
temp.leftChild = root; // 将失衡点root作为temp左子树
// 重新调整失衡点及其右孩子节点的高度值
root.height = Math.max(getHeight(root.leftChild), getHeight(root.rightChild)) + 1;
temp.height = Math.max(getHeight(temp.leftChild), getHeight(temp.rightChild)) + 1;
return temp; // 失衡点root的右孩子成为新的根节点(取代原失衡点的位置)
}
public static Tree leftAndRight(Tree root)//先左旋再右旋
{
root.leftChild=leftRotate(root.leftChild);//先以失衡点root的左孩子作为根节点进行左旋
return rightRotate(root);//再以失衡点root作为根节点进行右旋
}
public static Tree rightAndLeft(Tree root)//先右旋再左旋
{
root.rightChild=rightRotate(root.rightChild);//先以失衡点root的右孩子作为根节点进行右旋
return leftRotate(root);//再以失衡点root作为根节点进行左旋
}
public static Tree findNextNode(Tree root)//找到右子树下最小的节点
{
if(root == null)
{
return null;
}
Tree r = root.rightChild;
while(r != null && r.leftChild != null)
{
r = r.leftChild;
}
return r;
}
插入:
public static Tree insert(Tree root, int data)
{
if (root == null) //如果到的根节点为null,那么就在此处插入那个新的节点,用来终止递归
{
root = new Tree();
root.data=data;
return root;
}
if (data <= root.data)
{
root.leftChild = insert(root.leftChild, data);// 如果要插的值小于或等于当前的节点值,插入进其左子树
if (getHeight(root.leftChild) - getHeight(root.rightChild) > 1) //当平衡因子大于等于2时
{ // 由于是向当前root树的左边插入,所以左子树高度必定不小于右子树高度
if (data <= root.leftChild.data)
{ // 最后插入的叶子结点在该root树的左孩子的左边
root = rightRotate(root); // 右旋,然后将调整后的root返回给其父节点的左子树域
}
else
{ // 最后插入的叶子结点在该root树的左孩子的右边
root = leftAndRight(root);// 先左旋再右旋,然后将调整后的root返回给其父节点的左子树域
}
}
}
else
{
root.rightChild = insert(root.rightChild, data);// 如果要插的值大于当前的节点值,插入进其右子树
if(getHeight(root.rightChild) - getHeight(root.leftChild) > 1)
{
if(data <= root.rightChild.data)
{
root = rightAndLeft(root);// 先右旋再左旋,然后将调整后的root返回给其父节点的右子树域
}
else
{
root = leftRotate(root);// 左旋,然后将调整后的root返回给其父节点的右子树域
}
}
}
// 当插入新的节点后,从这个节点到根节点的最短路径上的节点的高度值一定会发生改变,另外当出现失衡点时,这个失衡点的所有祖先节点的高度值也可能会发生改变
root.height = Math.max(getHeight(root.leftChild), getHeight(root.rightChild)) + 1; // 重新调整root节点的高度值
return root;
}
删除:
public static Tree remove(Tree root, int data)
{
if(root == null)
{ // 没有找到删除的节点
return null;
}
if(data < root.data)
{ // 在左子树上删除
root.leftChild = remove(root.leftChild, data);
if(getHeight(root.rightChild) - getHeight(root.leftChild) > 1)
{ // 在左子树上删除,右子树高度一定不小于左子树高度
if(getHeight(root.rightChild.leftChild) > getHeight(root.rightChild.rightChild))
{
root = rightAndLeft(root);
}
else
{
root = leftRotate(root);
}
}
}
else if(data == root.data)
{ // 找到删除的节点
if(root.leftChild != null && root.rightChild != null)
{ // 删除的节点既有左子树又有右子树
root.data = findNextNode(root).data; // 将失衡点的data域更改为其直接后继节点的data域
root.rightChild = remove(root.rightChild, root.data); // 将问题转换为删除其直接后继节点
}else
{ // 只有左子树或者只有右子树或者为叶子结点的情况
root = (root.leftChild == null) ? root.rightChild : root.leftChild;
}
}
else
{ // 在root的右子树上查找删除节点
root.rightChild = remove(root.rightChild, data);
if(getHeight(root.leftChild) - getHeight(root.rightChild) > 1)
{
if(getHeight(root.leftChild.leftChild) > getHeight(root.leftChild.rightChild))
{
root = rightRotate(root);
}
else
{
root = leftAndRight(root);
}
}
}
if(root != null)
{ // 更新root的高度值
root.height = Math.max(getHeight(root.leftChild), getHeight(root.rightChild)) + 1;
}
return root;
}