平衡二叉树的概念
它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
平衡二叉树的作用
解决了二叉查找树退化成链表的问题,把插入,查找,删除的时间复杂度最好情况和最坏情况都维持在O(logN)。但是频繁旋转会使插入和删除牺牲掉O(logN)左右的时间,不过相对二叉查找树来说,时间上稳定了很多。
平衡二叉树的节点结构
package er_cha_shu;
/**
* 平衡二叉树的结点的定义
* @author
*
*/
public class Avlnode {
int data;//数据域
int bf;//平衡因子
Avlnode right,left,parent;//左子女,右子女,双亲节点
}
平衡二叉树的增删查
平衡二叉树的创建与增加操作是用一段代码实现。
平衡二叉树的增删查操作基本与二叉搜索树的操作一致。
值得注意的是,增加操作和删除操作时,破坏平衡二叉树的平衡,需要进行旋转操作
旋转操作
-
左旋转:
-
右旋转:
注意
- 如果左旋转图片中Aug有右子女,旋转后将右子女挂到Mar的左子女节点上
- 如果右旋转图片中May有左子女,旋转后将左子女挂到Mar的右子女节点上
代码
/**
* 具体最小的左旋转
* @param node
*/
public static void rotateLeft(Avlnode node)
{
Avlnode noderight,childleft,parent;
if (node != null) {
noderight = node.right;
childleft = noderight.left;
parent = node.parent;
noderight.parent = parent;
node.parent = noderight;
noderight.left = node;
if(childleft != null)
{
node.right = childleft;
childleft.parent = node;
}else {
node.right = null;
}
if(parent == null) {
root = noderight;
}else {
if(parent.left == node) {
parent.left = noderight;
}else {
parent.right = noderight;
}
}
}
}
/**
* 具体最小右旋
* @param node
*/
public static void rotateRight(Avlnode node)
{
Avlnode nodeleft,childright,parent;
if(node != null)
{
nodeleft = node.left;
parent = node.parent;
childright = nodeleft.right;
nodeleft.parent = parent;
node.parent = nodeleft;
nodeleft.right = node;
if(childright != null)
{
node.left = childright;
childright.parent = node;
}else {
node.left = null;
}
if(parent == null) {
root = nodeleft;
}else {
if(parent.left == node) {
parent.left = nodeleft;
}else {
parent.right = nodeleft;
}
}
}
}
创建节点/添加节点
public static boolean add(int element)
{
Avlnode current,parent,tmp;
//创建新建节点
current = new Avlnode();
current.data = element;
current.bf = 0;
current.left = current.right = current.parent = null;
//未创建根节点,先创建根节点
if (null == root) {
root = new Avlnode();
root = current;
return true;
}
//寻找新建节点(不是第一次创建新节点)的位置
tmp = root;
do {
parent = tmp;
if(tmp.data > current.data)
{
tmp = tmp.left;
}else if(tmp.data < current.data) {
tmp = tmp.right;
}else {
return false;
}
}while(tmp != null);
//将新建节点挂上
if (parent.data < element) {
parent.right = current;
current.parent = parent;
}else if (parent.data > element) {
parent.left = current;
current.parent = parent;
}
//找到最小要调整的父亲节点
while(parent != null)
{
if(current.data < parent.data)
{
parent.bf++;
}else {
parent.bf--;
}
//如果parent的平衡因子是0,说明parent的祖先节点的平衡因子不会受到新建节点的影响,故跳出循环
if(parent.bf == 0) {
break;
}else if(Math.abs(parent.bf) == 2) {//平衡因子为2或者-2时,从这个节点开始向上调整
fixbalance(parent);
break;
}
parent = parent.parent;
}
return true;
}
创建节点后的调整
总共有两种调整:调整节点node的bf=2和调整节点node的bf=-2。
-
调整节点node的bf=2(设node的左孩子为left):
- left的bf=1时
- left的bf=-1时
- left的bf=0时
- left的bf=1时
-
调整节点node的bf=-2(设node的右孩子为right)
- right的bf=1时
- right的bf=-1时
- right的bf=0时
- right的bf=1时
代码
/**
* 插入后的调整
* @param parent
*/
public static void fixbalance(Avlnode parent) {
if(parent.bf == 2)
{
leftBalance(parent);//左调整
}
if (parent.bf == -2) {
rightBalance(parent);//右调整
}
}
/**
* 右调整
* @param parent
*/
public static boolean rightBalance(Avlnode parent) {
boolean heightLower = true;
Avlnode right,childleft;
right = parent.right;
childleft = right.left;
switch(right.bf)
{
case 1://左高,分情况调整
switch(childleft.bf)
{
case 1:
parent.bf = 0;
right.bf = -1;
break;
case 0:
parent.bf = right.bf = 0;
break;
case -1:
parent.bf = 1;
right.bf = 0;
break;
}
childleft.bf = 0;
rotateRight(right);
rotateLeft(parent);
break;
case -1:
if(childleft==null)
{
parent.bf = right.bf = -1;
}else {
parent.bf = right.bf = 0;
}
rotateLeft(parent);
break;
case 0:
right.bf = 1;
parent.bf = -1;
rotateLeft(parent);
heightLower = false;
break;
}
return heightLower;
}
/**
* 左调整
* @param parent
*/
public static boolean leftBalance(Avlnode parent) {
boolean heightLower = true;
Avlnode left,childright;
left = parent.left;
childright = left.right;
switch(left.bf)
{
case 1:
if(childright==null)
{
parent.bf = left.bf = -1;
}else {
parent.bf = left.bf = 0;
}
rotateRight(parent);
break;
case -1:
switch(childright.bf)
{
case 1:
parent.bf = -1;
left.bf = 0;
break;
case 0:
parent.bf = left.bf = 0;
break;
case -1:
parent.bf = 0;
left.bf = 1;
break;
}
childright.bf = 0;
rotateLeft(left);
rotateRight(parent);
break;
case 0:
parent.bf = 1;
left.bf = -1;
rotateRight(parent);
heightLower = false;
break;
}
return heightLower;
}
删除节点
/**
* 删除节点
* @param shu
* @param root
*/
public static void delete(int shu,Avlnode root)
{
Avlnode node,replace;
node = (null!=find(shu,root)?find(shu,root):null);
if(node == null)
{
return;
}
//如果p左右子树都不为空,找到其直接后继,替换p,之后p指向s,删除p其实是删除s
//所有的删除左右子树不为空的节点都可以调整为删除左右子树有其一不为空,或都为空的情况。
if (node.left != null && node.right != null) {
Avlnode s = succeeor(node);
node.data = s.data;
node = s;
}
replace = (node.left != null ? node.left:node.right);
if (replace != null)
{//如果左右子树有其一不为空
replace.parent = node.parent;
if (node.parent ==null) {
root = replace;
}else if(node == node.parent.left) {//如果node是左孩子
node.parent.left = replace;
}else {//如果node是右孩子
node.parent.right = replace;
}
node.left = node.parent = node.right = null;//node的指针清空
//更改了replace的父节,所以直接从它开始向上回溯
fixAfterDeletion(replace);
}else if(node.parent == null) { //如果全树只有一个节点
root = null;
}else {
fixAfterDeletion(node);
if(node.parent != null)
{
if (node == node.parent.left) {
node.parent.left = null;
}else if(node == node.parent.right) {
node.parent.right = null;
}
node.parent = null;
}
}
}
删除节点的调整
/**
* 删除某节点p后的调整方法:
* 1.从p开始向上回溯,修改祖先的BF值,这里只要调整从p的父节点到根节点的BF值,
* 调整原则为,当p位于某祖先节点(简称A)的左子树中时,A的BF减1,当p位于A的
* 右子树中时A的BF加1。当某个祖先节点BF变为1或-1时停止回溯,这里与插入是相反的,
* 因为原本这个节点是平衡的,删除它的子树的某个节点并不会改变它的高度
*
* 2.检查每个节点的BF值,如果为2或-2需要进行旋转调整,调整方法如下文,
* 如果调整之后这个最小子树的高度降低了,那么必须继续从这个最小子树的根节点(假设为B)继续
* 向上回溯,这里和插入不一样,因为B的父节点的平衡性因为其子树B的高度的改变而发生了改变,
* 那么就可能需要调整,所以删除可能进行多次的调整。
*
*/
public static void fixAfterDeletion(Avlnode node) {
boolean heightLower = true;
Avlnode t = node.parent;
while(t != null && heightLower)
{
if (node.data >= t.data) {
t.bf++;
} else {
t.bf--;
}
if (Math.abs(t.bf)==1) {//父节点经过调整平衡因子后,如果为1或-1,说明调整之前是0,停止回溯。
break;
}
Avlnode t1 = t;
//这里的调整跟插入一样
if (t.bf == 2) {
heightLower = leftBalance(t1);
} else if(t.bf == -2){
heightLower = rightBalance(t1);
}
t = t.parent;
}
}
查找节点
/**
* 查找指定的数字
* @param shu 要查找的数字
* @param root 根节点
* @return
*/
public static Avlnode find(int shu,Avlnode root)
{
Avlnode node = root;
while(node != null)
{
if (node.data > shu) {
node = node.left;
} else if(node.data < shu){
node = node.right;
}else {
return node;
}
}
return null;
}
源代码
package er_cha_shu;
import java.util.LinkedList;
import java.util.Queue;
/**
* 二叉平衡树的相关方法
* 左子树比右子树的高度大记为正,相等记为0,小于记为负
* @author abi
*
*/
public class Avl {
static Avlnode root;//根节点静态化
public static boolean add(int element)
{
Avlnode current,parent,tmp;
//创建新建节点
current = new Avlnode();
current.data = element;
current.bf = 0;
current.left = current.right = current.parent = null;
//未创建根节点,先创建根节点
if (null == root) {
root = new Avlnode();
root = current;
return true;
}
//寻找新建节点(不是第一次创建新节点)的位置
tmp = root;
do {
parent = tmp;
if(tmp.data > current.data)
{
tmp = tmp.left;
}else if(tmp.data < current.data) {
tmp = tmp.right;
}else {
return false;
}
}while(tmp != null);
//将新建节点挂上
if (parent.data < element) {
parent.right = current;
current.parent = parent;
}else if (parent.data > element) {
parent.left = current;
current.parent = parent;
}
//找到最小要调整的父亲节点
while(parent != null)
{
if(current.data < parent.data)
{
parent.bf++;
}else {
parent.bf--;
}
//如果parent的平衡因子是0,说明parent的祖先节点的平衡因子不会受到新建节点的影响,故跳出循环
if(parent.bf == 0) {
break;
}else if(Math.abs(parent.bf) == 2) {//平衡因子为2或者-2时,从这个节点开始向上调整
fixbalance(parent);
break;
}
parent = parent.parent;
}
return true;
}
/**
* 插入后的调整
* @param parent
*/
public static void fixbalance(Avlnode parent) {
if(parent.bf == 2)
{
leftBalance(parent);//左调整
}
if (parent.bf == -2) {
rightBalance(parent);//右调整
}
}
/**
* 右调整
* @param parent
*/
public static boolean rightBalance(Avlnode parent) {
boolean heightLower = true;
Avlnode right,childleft;
right = parent.right;
childleft = right.left;
switch(right.bf)
{
case 1://左高,分情况调整
switch(childleft.bf)
{
case 1:
parent.bf = 0;
right.bf = -1;
break;
case 0:
parent.bf = right.bf = 0;
break;
case -1:
parent.bf = 1;
right.bf = 0;
break;
}
childleft.bf = 0;
rotateRight(right);
rotateLeft(parent);
break;
case -1:
if(childleft==null)
{
parent.bf = right.bf = -1;
}else {
parent.bf = right.bf = 0;
}
rotateLeft(parent);
break;
case 0:
right.bf = 1;
parent.bf = -1;
rotateLeft(parent);
heightLower = false;
break;
}
return heightLower;
}
/**
* 左调整
* @param parent
*/
public static boolean leftBalance(Avlnode parent) {
boolean heightLower = true;
Avlnode left,childright;
left = parent.left;
childright = left.right;
switch(left.bf)
{
case 1:
if(childright==null)
{
parent.bf = left.bf = -1;
}else {
parent.bf = left.bf = 0;
}
rotateRight(parent);
break;
case -1:
switch(childright.bf)
{
case 1:
parent.bf = -1;
left.bf = 0;
break;
case 0:
parent.bf = left.bf = 0;
break;
case -1:
parent.bf = 0;
left.bf = 1;
break;
}
childright.bf = 0;
rotateLeft(left);
rotateRight(parent);
break;
case 0:
parent.bf = 1;
left.bf = -1;
rotateRight(parent);
heightLower = false;
break;
}
return heightLower;
}
/**
* 具体最小的左旋转
* @param node 平衡性为2的节点
*/
public static void rotateLeft(Avlnode node)
{
//noderight
Avlnode noderight,childleft,parent;
if (node != null) {
noderight = node.right;
childleft = noderight.left;
parent = node.parent;
noderight.parent = parent;
node.parent = noderight;
noderight.left = node;
if(childleft != null)
{
node.right = childleft;
childleft.parent = node;
}else {
node.right = null;
}
if(parent == null) {
root = noderight;
}else {
if(parent.left == node) {
parent.left = noderight;
}else {
parent.right = noderight;
}
}
}
}
/**
* 具体最小右旋
* @param node
*/
public static void rotateRight(Avlnode node)
{
Avlnode nodeleft,childright,parent;
if(node != null)
{
nodeleft = node.left;
parent = node.parent;
childright = nodeleft.right;
nodeleft.parent = parent;
node.parent = nodeleft;
nodeleft.right = node;
if(childright != null)
{
node.left = childright;
childright.parent = node;
}else {
node.left = null;
}
if(parent == null) {
root = nodeleft;
}else {
if(parent.left == node) {
parent.left = nodeleft;
}else {
parent.right = nodeleft;
}
}
}
}
/**
* 查找指定的数字
* @param shu 要查找的数字
* @param root 根节点
* @return
*/
public static Avlnode find(int shu,Avlnode root)
{
Avlnode node = root;
while(node != null)
{
if (node.data > shu) {
node = node.left;
} else if(node.data < shu){
node = node.right;
}else {
return node;
}
}
return null;
}
/**
* 删除节点
* @param shu
* @param root
*/
public static void delete(int shu,Avlnode root)
{
Avlnode node,replace;
node = (null!=find(shu,root)?find(shu,root):null);
if(node == null)
{
return;
}
//如果p左右子树都不为空,找到其直接后继,替换p,之后p指向s,删除p其实是删除s
//所有的删除左右子树不为空的节点都可以调整为删除左右子树有其一不为空,或都为空的情况。
if (node.left != null && node.right != null) {
Avlnode s = succeeor(node);
node.data = s.data;
node = s;
}
replace = (node.left != null ? node.left:node.right);
if (replace != null)
{//如果左右子树有其一不为空
replace.parent = node.parent;
if (node.parent ==null) {
root = replace;
}else if(node == node.parent.left) {//如果node是左孩子
node.parent.left = replace;
}else {//如果node是右孩子
node.parent.right = replace;
}
node.left = node.parent = node.right = null;//node的指针清空
//更改了replace的父节,所以直接从它开始向上回溯
fixAfterDeletion(replace);
}else if(node.parent == null) { //如果全树只有一个节点
root = null;
}else {
fixAfterDeletion(node);
if(node.parent != null)
{
if (node == node.parent.left) {
node.parent.left = null;
}else if(node == node.parent.right) {
node.parent.right = null;
}
node.parent = null;
}
}
}
/**
* 删除某节点p后的调整方法:
* 1.从p开始向上回溯,修改祖先的BF值,这里只要调整从p的父节点到根节点的BF值,
* 调整原则为,当p位于某祖先节点(简称A)的左子树中时,A的BF减1,当p位于A的
* 右子树中时A的BF加1。当某个祖先节点BF变为1或-1时停止回溯,这里与插入是相反的,
* 因为原本这个节点是平衡的,删除它的子树的某个节点并不会改变它的高度
*
* 2.检查每个节点的BF值,如果为2或-2需要进行旋转调整,调整方法如下文,
* 如果调整之后这个最小子树的高度降低了,那么必须继续从这个最小子树的根节点(假设为B)继续
* 向上回溯,这里和插入不一样,因为B的父节点的平衡性因为其子树B的高度的改变而发生了改变,
* 那么就可能需要调整,所以删除可能进行多次的调整。
*
*/
public static void fixAfterDeletion(Avlnode node) {
boolean heightLower = true;
Avlnode t = node.parent;
while(t != null && heightLower)
{
if (node.data >= t.data) {
t.bf++;
} else {
t.bf--;
}
if (Math.abs(t.bf)==1) {//父节点经过调整平衡因子后,如果为1或-1,说明调整之前是0,停止回溯。
break;
}
Avlnode t1 = t;
//这里的调整跟插入一样
if (t.bf == 2) {
heightLower = leftBalance(t1);
} else if(t.bf == -2){
heightLower = rightBalance(t1);
}
t = t.parent;
}
}
/**
* 找直接后继
* @param node
* @return
*/
public static Avlnode succeeor(Avlnode node) {
if (node == null) {
return null;
} else if(node.right !=null){
Avlnode right = node.right;
while(right.left != null) {
right = right.left;
}
return right;
}else {
Avlnode p = node.parent;
Avlnode r = node;
while(p != null && r == p.right)
{
r = p;
p = p.parent;
}
return p;
}
}
public static void preOrder(Avlnode root)
{
if(root != null)
{
System.out.println(root.data+"\t"+root.bf+"\t");
preOrder(root.left);
preOrder(root.right);
}
}
public static void inOrder(Avlnode root)
{
if(root != null)
{
inOrder(root.left);
System.out.println(root.data+"\t"+root.bf+"\t");
inOrder(root.right);
}
}
public static void main(String[] args) {
int[] arr = {3,6,9,2,5,4,8,1};
for (int i = 0; i < arr.length; i++) {
add(arr[i]);
}
preOrder(root);
System.out.println("--------------------");
inOrder(root);
delete(8, root);
System.out.println("--------------------");
preOrder(root);
}
}