文章目录
AVL树的定义
- Adelson-Velsky-Landis Tree,是最先发明的自平衡二叉查找树,由作者名字命名
- 一棵AVL树或者是空树,或者是具有下列性质的二叉排序树:
1.它的左子树和右子树都是AVL树,
2.且左子树和右子树的高度之差的绝对值不超过1
3.(记住要统一,都是右减左,或都是左减右)。
AVL 树结构设计
class AVLTree {
class AVLNode {
AVLNode leftchild;
AVLNode parent;
AVLNode rightchild;
int balance; // -1 ,0 , 1
int key;
public AVLNode() {
}
public AVLNode(int x) {
key = x;
balance=0;
leftchild = parent = leftchild = null;
}
}
private AVLNode root;
private AVLNode cur;
public AVLTree() {
this.root = null;
this.cur = null;
}
}
单旋转
左单旋(RotateLeft )
//左单旋 ptr是发现的第一个不平衡的子树的根 先主干,再完善 一个函数只完成一个单一的功能,所以不管平衡因子的事
private void RotateLeft(AVLNode ptr) {
//第1步(newroot代替ptr,指向原来ptr的父亲)
AVLNode newroot = ptr.rightchild;
newroot.parent = ptr.parent;
//第2步
ptr.rightchild = newroot.leftchild;//父指向子
if (newroot.leftchild != null) //子若有,就指向父
{
newroot.leftchild.parent = ptr;
}
//第3步
newroot.leftchild = ptr;
//ptr原来的父 指向 新子
AVLNode pa = ptr.parent;
if (pa == null)//说明ptr是根节点
{
root = newroot;
} else {
//原来ptr挂在双亲的左边,现在旋转后的新根就仍然挂在左边
if (pa.leftchild == ptr) {
pa.leftchild = newroot;
} else {
pa.rightchild = newroot;
}
}
//上面处理完了ptr原来的父,现在ptr就可以指向自己的新父
ptr.parent = newroot;
}
右单旋 (RotateRight )
/*
右单旋 和左旋为镜像
代码分为三块:
旋转(1 2 3步)
维护双亲
维护新根
*/
private void RotateRight(AVLNode ptr) {
AVLNode newroot = ptr.leftchild;//旋转1
newroot.parent = ptr.parent;
ptr.leftchild = newroot.rightchild;//旋转2 newroot.rightchild可能为null
if (newroot.rightchild != null) {
newroot.rightchild.parent = ptr;
}
newroot.rightchild = ptr;//旋转3
AVLNode pa = ptr.parent;//ptr 和 newroot从头到尾指向各自的同一个结点,没变过!!!!
if (pa == null) {
root = newroot;
} else {
if (pa.leftchild == ptr) {
pa.leftchild = newroot;
} else {
pa.rightchild = newroot;
}
}
ptr.parent = newroot;//旋转3
}
双选转
先左后右双旋转 (RotationLeftRight) // LeftBalance
- 要保证中序遍历有序,以及各节点平衡因子尽可能小
- 如下图,是所有可能进行双旋的情况,h为0是特殊情况。
- 本来右比左低一个,在左子树(D、F、E处)又插一个,导致根(A)的左子树已经失去平衡了,所以调整函数又叫LeftBalance
- 看图发现,插入新结点前 和 插入新节点并调整后,层高并没有发生改变
// 先左后右双旋转(保证中序有序 以及 各节点平衡因子尽可能小) 好像不会出现平衡因子大于1或小于-1的,因为插入的时候发现不平衡立马就进行旋转调整了
private void LeftBalance(AVLNode ptr) {
// wps图,ptr指向根(即A)!!! leftsub指向B!!!
AVLNode leftsub = ptr.leftchild, rightsub = null;
switch (leftsub.balance) {
case 0:
System.out.println("left balance \n");
break;
case -1: //即B的右子树-B左子树的深度=-1 即插到D下面(看图知,调整后平衡因子为0)
ptr.balance = 0;
leftsub.balance = 0;//这里用不到rightsub,即E的平衡因子没有变化
//不管是在D的左孩子还是右孩子插入,总之深度+1了,旋转方式是一样的
RotateRight(ptr);
break;
case 1:// 即B的右子树比左大1,即插入在B的右树 rightsub指向E!!!
rightsub = leftsub.rightchild;
//先专门调整 双旋后的banlance值
switch (rightsub.balance) {
case 1://1即G处插入
ptr.balance = 0;
leftsub.balance = -1;
break;
case 0:
ptr.balance = 0;
leftsub.balance = 0;
break;
case -1://-1即F处插入
ptr.balance = 1;
leftsub.balance = 0;
break;
}
rightsub.balance = 0;
//即F 或 G处插入,旋转方式是一样的
RotateLeft(leftsub);//即以B结点为根的树
RotateRight(ptr);// A
break;
}
}
先右后左双旋转 (RotationRightLeft) // RightBalance
右子树失去平衡,需要先右旋再左旋,故为函数名称由来(和 先左旋后右旋相似)
如图:在F、G、E处插入都需要双旋
以在G处插入调整为例:
中序遍历顺序:BAFDGCE
// 先右后左双旋转(wps图)
private void RightBalance(AVLNode ptr) { //ptr即A rightsub即C !!!
AVLNode rightsub = ptr.rightchild, leftsub = null;
switch (rightsub.balance) {
case 0:
System.out.println("right balance \n");
break;
case 1://E处插放入
ptr.balance = 0;
rightsub.balance = 0;
RotateLeft(ptr);
break;
case -1: //D的子树里插入
//leftsub即D !!!
leftsub = rightsub.leftchild;
switch (leftsub.balance) {
case 1://G处插入
ptr.balance = -1;//可以看图想象 也可以对比LeftBalance
rightsub.balance = 0;
break;
case 0:
ptr.balance = 0;
rightsub.balance = 0;
break;
case -1://F处插入
ptr.balance = 0;
rightsub.balance = 1;
break;
}
leftsub.balance = 0;
RotateRight(rightsub);
RotateLeft(ptr);
break;
}
}
调整
可以参照前面单旋、双旋的图片理解
private void Adjust_AVL(AVLNode ptr) {
AVLNode pa = ptr.parent;
boolean high = true;//标志高度是否发生改变
//若ptr的双亲为空(即为根) 或 插入后层高没变 或 将由底往上第一个不平衡节点的子树调整好了,就不用 再 进行调整了
while (high && pa != null) {
//说明ptr插入到了其夫(pa)的左边
if (pa.leftchild == ptr) {
switch (pa.balance) {
case 0: //即本来平衡因子为0,插入到左边,右-左=-1,pa高度发生了改变!!!(需要下轮while调整其父的平衡因子)
pa.balance = -1;
break;
case 1://本来右-左=1 现在插入左,右-左=0,以pa为根的树的最大高度没有发生改变
pa.balance = 0;
high = false;
break;
case -1:
LeftBalance(pa);//左边不平衡了,所以调用该算法(里面有调整平衡因子),旋转后的层高 和 插入前的层高没有改变(故无需调整其父的平衡因子)
high = false;
break;
}
}
else {
switch (pa.balance) {
case 0:
pa.balance = 1;
break;
case -1:
pa.balance = 0;
high = false;
break;
case 1:
RightBalance(pa);
high = false;
break;
}
}
//调整完 逐层往上找,该变 其父节点的平衡因子(针对 case 0:)
ptr = pa;
pa = ptr.parent;
}
}// 11:23
插入
插入方法同BST树的INSERT,只是多了一步插入后对树的调整。
// 插入方法同BST树的INSERT;
public boolean Insert(int kx) {
boolean res = true;
//空树时
if (root == null) {
root = new AVLNode(kx);
return res;
}
cur = root;
AVLNode pa = null;
//在合适的分支找到底为止
while (cur != null && cur.key != kx) {
pa = cur;//找cur的孩子,所以升级当爸了
cur = kx < cur.key ? cur.leftchild : cur.rightchild;
}
//有该值,就不插入(false,因为不允许重复)
if (cur != null && cur.key == kx) {
res = false;
} else {
cur = new AVLNode(kx);
cur.parent = pa;//子指向父
//没有该值,就将新结点挂在上面查找的最后一个位置的左或右孩子
if (cur.key < pa.key) {
pa.leftchild = cur;//父指向子
} else {
pa.rightchild = cur;
}
//链接好双亲后从插入的数据点处开始调整
Adjust_AVL(cur);
}
return res;
}