一棵Avl树是其每个节点的左子树和右子树的高度最多差1的二叉查找树
1. 定义节点
//建立带高度的节点
private static class AvlNode<AnyType>
{
AnyType element;
AvlNode<AnyType> left;
AvlNode<AnyType> right;
int height;//为了Avl特点
AvlNode(AnyType x)
{
this(x, null, null);
}
AvlNode (AnyType x, AvlNode<AnyType> left, AvlNode<AnyType> right)
{
element = x;
this.left = left;
this.right = right;
height = 0;
}
}
- 与普通二叉查找树的节点不同的是,平衡二叉树的节点增加了height属性,这是为了满足平衡二叉树的定义而设定的
2.定义树的根
//根
private AvlNode<AnyType> root;
- 根是私有的
3.初始化平衡二叉树
//初始化Avl树
public AvlTree()
{
root = null;
}
- 使用构造函数初始化平衡二叉树
4.清空二叉树
//makeEmpty方法
public void makeEmpty()
{
root = null;
}
5.判断二叉树是否为空
//isEmpty方法
public boolean isEmpty()
{
return root == null;
}
- 返回类型是boolean型
6.判断二叉树中是否包含某个数据
//第一部分:对外显示的方法
public boolean contains(AnyType x)
{
return contains(x, root);
}
- return contains(x, root);中的root表示从根开始判断
//第二部分:私有方法
//采用递归
private boolean contains(AnyType x, AvlNode<AnyType> t)
{
if(t == null)
return false;
int compareResult = x.compareTo(t.element);//将x与t节点的值比较
if(compareResult < 0)
return contains(x, t.left);
else if(compareResult > 0)
return contains(x, t.right);
else
return true;
}
//采用循环
private boolean contains(AnyType x, AvlNode<AnyType> t)
{
int compareResult = x.compareTo(t.element);
while(t != null)
{
if(compareResult < 0)
t = t.left;
else if(compareResult > 0)
t = t.right;
else
return true;
}
return false;
- contains的思想是将要判断的数据先与根节点的值比较,若小于根节点的值,则找根节点的左子树…以此类推,这也是二叉查找树的属性:对于树中的每个节点x,它的左子树中所有项的值小于X中的项,它的右子树中的所有项的值大于X中的项
7.寻找树中最小值
//findMin方法:使用递归
public AnyType findMin()
{
if(isEmpty())
throw new RuntimeException();
return findMin(root).element;
}
private AvlNode<AnyType> findMin(AvlNode<AnyType> t)
{
if(t == null)
return null;
else if (t.left == null)
return t;
else
return findMin(t.left);
}
- 二叉树中的最小值位于最左端
- 当二叉树为空时抛出异常:无法执行该方法
- 私有的findMin方法返回的是一个节点,这样包含的信息更多,方面后面的使用
8.寻找树中最大值
//findMax方法:使用循环
public AnyType findMax()
{
if(isEmpty())
throw new RuntimeException();
return findMax(root).element;
}
private AvlNode<AnyType> findMax(AvlNode<AnyType> t)
{
if(t == null)
return null;
while(t.right != null)
t = t.right;
return t;
}
9.向树中添加一项
//insert方法
public void insert(AnyType x)
{
root = insert(x,root);
}
private AvlNode<AnyType> insert(AnyType x, AvlNode<AnyType> t)
{
if(t == null)
return new AvlNode<AnyType>(x, null, null);
int compareResult = x.compareTo(t.element);
if(compareResult < 0)
t.left = insert(x, t.left);//在t的左子树中继续寻找应在位置
else if (compareResult > 0)
t.right = insert(x, t.right);
else
;
return balance(t);
}
- 大部分的私有方法返回的都是节点而不是某项数据,这样包含的信息更多,提高代码复用性
- 首先创建一个数据项是要求值的节点,再经过判断将节点置于应在位置
- 在return时调用一次balance方法使得该二叉树平衡
10.从树中移除某项
//remove方法
public void remove(AnyType x)
{
root = remove(x, root);
}
private AvlNode<AnyType> remove(AnyType x, AvlNode<AnyType> t)
{
if(t == null)
return null;
int compareResult = x.compareTo(t.element);
if(compareResult < 0)
t.left = remove(x, t.left);
else if(compareResult > 0)
t.right = remove(x, t.right);
else if(t.left != null && t.right != null)//被移除节点有两个子树
{
t.element = findMin(t.right).element;
t.right = remove(t.element, t.right);
}
else // 被移除节点只有一个子树
t = (t.left != null)? t.left : t.right;
return balance(t);
}
- 首先找到要被移除的节点
- 若该节点有两个子树,那么选取该节点右子树中最小的值来代替 t 节点的值,然后递归地在t节点的右子树中删除这个值的节点(这个节点不可能有左儿子)
- 若该节点只有一个子树,那么用该子树节点的值代替t 的值
- 返回时仍然需要平衡
11.打印该树
//printTree方法
public void printTree()
{
printTree(root);
}
private void printTree(AvlNode<AnyType> t)
{
if(t != null)
printTree(t.left);
System.out.println(t.element);
printTree(t.right);
}
- 采用中序打印
12.求一个节点的高度
//height方法
private int height(AvlNode<AnyType> t)
{
if(t == null)
return -1;
else
return t.height;
}
- 空节点的高度为-1
13.平衡该二叉树
//balance方法
private static final int ALLOWED_IMBALANCE = 1;
private AvlNode<AnyType> balance(AvlNode<AnyType> t)
{
if(t == null)
return t;
if(height(t.left) - height(t.right) > ALLOWED_IMBALANCE )//左子树高于右子树
if (height(t.left.left) >= height(t.left.right) )//左左//等号对应的是删除现象
t = rotateWithLeftChild(t);
else //左右
t = doubleWithLeftChild(t);
else if(height(t.right) - height(t.left) > ALLOWED_IMBALANCE )//右子树高于左子树
if(height(t.right.right) >= height(t.right.left))//右右
t = rotateWithRightChild(t);
else//右左
t = doubleWithRightChild(t);
t.height = Math.max(height(t.left), height(t.right)) + 1;
return t;
}
-
一个节点a的不平衡现象有四种
1.对a的左儿子的左子树进行一次插入(或是一次删除操作后等效于此情况)
2.对a的左儿子的右子树进行一次插入(或是一次删除操作后等效于此情况)
3.对a的右儿子的左子树进行一次插入(或是一次删除操作后等效于此情况)
4.对a的右儿子的右子树进行一次插入(或是一次删除操作后等效于此情况) -
对情况1、4,进行一次单旋转就足够了
-
对情况2、3,需要进行双旋转
14.两种单旋转
//左左单旋转
private AvlNode<AnyType> rotateWithLeftChild(AvlNode<AnyType> k2)
{
AvlNode<AnyType> k1 = k2.left;
k2.left = k1.right;
k1.right = k2;
k2.height = Math.max(height(k2.left), height(k2.right)) + 1;
k1.height = Math.max(height(k1.left), height(k2)) + 1;
return k1;
}
//右右单旋转
private AvlNode<AnyType> rotateWithRightChild(AvlNode<AnyType> k1)
{
AvlNode<AnyType> k2 = k1.right;
k1.right = k2.left;
k2.left = k1;
k1.height = Math.max(height(k1.left), height(k1.right)) + 1;
k2.height = Math.max(height(k1), height(k2.right)) + 1;
return k2;
}
15.两种双旋转
//左右双旋转
private AvlNode<AnyType> doubleWithLeftChild(AvlNode<AnyType> k3)
{
k3.left = rotateWithRightChild(k3.left);
return rotateWithLeftChild(k3);
}
//右左双旋转
private AvlNode<AnyType> doubleWithRightChild(AvlNode<AnyType> k1)
{
k1.right = rotateWithLeftChild(k1.right);
return rotateWithRightChild(k1);
}
- 双旋转相当于对a的儿子和孙子之间单旋转后再在a和它的新儿子之间单旋转