平衡二叉树(AVL树)
平衡二叉树也叫平衡二叉搜索树,又被称之为AVL树,可以保证查询效率较高
具有以下特点:它是一棵空树或者它的左右两个子树的高度差绝对值不超过1,并且左右两个子树都是一棵平衡二叉树.平衡二叉树的常用实现方法有红黑树,AVL,替罪羊树,Treap,伸展树等
AVL的节点删除
AVL的节点删除和二叉搜索树相同.
旋转操作
在添加节点时,为了使得整棵树处于平衡状态,需要进行AVL的旋转.
注意,旋转操作一定不能改变当前节点的指向(即修改前后,temp(当前节点)始终处于顶部,不能将创建的新节点置于顶部,否则当temp节点为根节点时,则需要额外操作来将新节点修改为整棵树的根节点)
左旋转
当右子树高度 - 左子树高度 > 1时:
- 以当前节点的值建立新节点
- 新节点的左边,牵上当前节点的左边
- 新节点的右边,牵上当前节点右节点的左节点
- 修改当前节点的id值,为其右边节点的值(连接尚未断开)
- 当前节点左边牵上新节点
- 当前节点的连接跳过复制节点id的那个节点(删除)
- 左旋转代码:
//左旋转(右子树高度 - 左子树高度 > 1)
private void leftRotate(Node temp)
{
//以当前节点的值建立新节点
Node newNode = new Node(temp.id);
//新节点的左边,牵上当前节点的左边
newNode.left = temp.left;
//新节点的右边,牵上当前节点右边的左边
newNode.right = temp.right.left;
//修改当前节点的id值,为其右边节点的值(连接尚未断开)
temp.id = temp.right.id;
//当前节点左边牵上新节点
temp.left = newNode;
//跳过复制节点id的那个节点(删除)
temp.right = temp.right.right;
}
右旋转
当左子树高度 - 右子树高度 > 1时:
- 以当前节点的值建立新节点
- 新节点的右边,牵上当前节点的右边
- 新节点的左边,牵上当前节点左节点的右节点
- 修改当前节点的id值,为其左节点的值(连接尚未断开)
- 当前节点右边牵上新节点
- 当前节点的连接跳过复制节点id的那个节点(删除)
- 图例如下:
- 右旋转代码:
//右旋转(左子树高度 - 右子树高度 > 1)
private void rightRotate(Node temp)
{
Node newNode = new Node(temp.id);
newNode.right = temp.right;
newNode.left = temp.left.right;
temp.id = temp.left.id;
temp.right = newNode;
temp.left = temp.left.left;
}
双旋转
- 左旋
->
右旋:
- 先满足右旋转的条件
- 当前节点左节点的右子树高度大于左子树的高度(内侧树高度大于外侧树高度)
- 先对当前节点的左节点进行左旋转
- 再对当前节点进行右旋转
- 右旋
->
左旋:
- 先满足左旋转的条件
- 当前节点右节点的左子树高度大于右子树的高度(内侧树高度大于外侧树高度)
- 先对当前节点的右节点进行右旋转
- 再对当前节点进行左旋转
旋转部分代码(所有旋转的逻辑),旋转操作增添至二叉排序树增加节点方法的尾部:
//满足左旋转条件
if(temp.rightHeight() - temp.leftHeight() > 1)
{
//内侧子树的高度大于外侧子树的高度,不能单纯地进行左旋,要进行双旋转
if(temp.right.leftHeight() > temp.right.rightHeight())
{
rightRotate(temp.right);
}
//整体左旋
leftRotate(temp);
}
//满足右旋转条件
else if(temp.leftHeight() - temp.rightHeight() > 1)
{
//内侧子树的高度大于外侧子树的高度,不能单纯地进行右旋,要进行双旋转
if(temp.left.rightHeight() > temp.left.leftHeight())
{
rightRotate(temp.left);
}
//整体右旋
rightRotate(temp);
}
AVL树完整类代码:
//平衡二叉树
public class AVLTree
{
//根节点
Node root;
//空参构造方法
public AVLTree() { }
//有参构造方法
public AVLTree(int id)
{
root = new Node(id);
}
/**
* 增加节点的函数,包含AVL的旋转操作
* @param temp temp为当前递归至的节点
* @param id id为需要增加的新节点的id
*/
public void addNode(Node temp,int id)
{
//新节点从属于在当前节点的左子树
if(id < temp.id)
{
//左边为空,直接挂在左边
if(temp.left == null)
{
temp.left = new Node(id);
return;
}
//左边不为空,则向左边递归
else
{
addNode(temp.left,id);
}
}
//新节点从属于当前节点的右子树
else
{
//右边为空,直接挂在左边
if(temp.right == null)
{
temp.right = new Node(id);
return;
}
//右边不为空,则向右边递归
else
{
addNode(temp.right,id);
}
}
//满足左旋转条件
if(temp.rightHeight() - temp.leftHeight() > 1)
{
//内侧子树的高度大于外侧子树的高度,不能单纯地进行左旋,要进行双旋转
if(temp.right.leftHeight() > temp.right.rightHeight())
{
rightRotate(temp.right);
}
leftRotate(temp);
}
//满足右旋转条件
else if(temp.leftHeight() - temp.rightHeight() > 1)
{
//内侧子树的高度大于外侧子树的高度,不能单纯地进行右旋,要进行双旋转
if(temp.left.rightHeight() > temp.left.leftHeight())
{
rightRotate(temp.left);
}
rightRotate(temp);
}
}
//方法重写
public void addNode(int id)
{
//根节点为空,则直接修改根节点
if(root == null)
{
root = new Node(id);
return;
}
//根节点不为空,执行之前写好的方法
addNode(root,id);
}
//中序遍历
public void traversal()
{
if(root == null)
{
System.out.println("二叉树为空!");
return;
}
//调用节点类中地中序遍历方法
root.traversal();
System.out.println(); //换行符
}
//删除节点
/**
* 删除节点方法重写
* @param target 当前目标节点
* @param id 要删除的节点id值
* @return 返回的节点为target连接的下一个节点
*/
public Node delNode(Node target,int id)
{
if(target.id == id)
{
//1. 要删除的是叶子节点
if(target.left == null && target.right == null)
{
return null;
}
//2. 要删除的节点,其含有左子树,但不含有右子树
else if(target.left != null && target.right == null)
{
return target.left;
}
//3. 要删除的节点,其含有左子树,但不含有右子树
else if(target.left == null && target.right != null)
{
return target.right;
}
//4. 要删除的节点,同时具有左子树和右子树
else
{
Node cur = target.right;
while(cur.left != null)
{
cur = cur.left;
}
cur.left = target.left;
return target.right;
}
}
//递归删除左边节点
else if(id < target.id)
{
target.left = delNode(target.left,id);
}
//递归删除右边节点
else if(id > target.id)
{
target.right = delNode(target.right,id);
}
return target;
}
//方法重写
public void delNode(int id)
{
if(root == null)
{
System.out.println("二叉树为空!无法删除!");
return;
}
//如果root即为要删除的节点
if(root.id == id)
{
//根节点为叶子节点,直接置空
if(root.left == null && root.right == null)
{
root = null;
}
//根节点左边不为空,右边为空
else if(root.left != null && root.right == null)
{
root = root.left;
}
//根节点左边为空,右边不为空
else if(root.left == null && root.right != null)
{
root = root.right;
}
//根节点左右节点皆不为空
else
{
Node cur = root.right;
while(cur.left != null)
{
cur = cur.left;
}
cur.left = root.left;
root = root.right;
}
return;
}
//根节点不是要删除的节点,则调用重写中的方法
Node nodeDel = delNode(root,id);
}
//左旋转(右子树高度 - 左子树高度 > 1)
private void leftRotate(Node temp)
{
//以当前节点的值建立新节点
Node newNode = new Node(temp.id);
//新节点的左边,牵上当前节点的左边
newNode.left = temp.left;
//新节点的右边,牵上当前节点右边的左边
newNode.right = temp.right.left;
//修改当前节点的id值,为其右边节点的值(连接尚未断开)
temp.id = temp.right.id;
//当前节点左边牵上新节点
temp.left = newNode;
//跳过复制节点id的那个节点(删除)
temp.right = temp.right.right;
}
//右旋转(左子树高度 - 右子树高度 > 1)
private void rightRotate(Node temp)
{
Node newNode = new Node(temp.id);
newNode.right = temp.right;
newNode.left = temp.left.right;
temp.id = temp.left.id;
temp.right = newNode;
temp.left = temp.left.left;
}
}
//节点类
class Node
{
int id;
Node left;
Node right;
//构造函数
public Node(int id)
{
this.id = id;
}
//转化为字符串的方法
@Override
public String toString()
{
return "id=" + id;
}
//遍历
public void traversal()
{
if(this.left != null)
{
this.left.traversal();
}
System.out.print(this + " ");
if(this.right != null)
{
this.right.traversal();
}
}
//以当前节点为根部的树的高度
public int height()
{
if(this == null)
{
return 0;
}
return Math.max(this.left == null ? 0 : this.left.height(),this.right == null ? 0 : this.right.height()) + 1;
}
//左子树的高度(递归方法求树高)
public int leftHeight()
{
return this.left == null ? 0 : this.left.height();
}
//右子树的高度(递归方法求树高)
public int rightHeight()
{
return this.right == null ? 0 : this.right.height();
}
}