一)AVL平衡二叉树简介
AVL平衡二叉树是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
优点:节点之间高度相差不大(小于2),搜索节点时会比较快。
缺点:新增或删除的时,可能导致二叉树失去平衡,需要重新旋转二叉树至平衡,性能稍微慢点。
平衡二叉树模型:
非平衡二叉树模型:根据非平衡二叉树转换成平衡二叉树,有四种旋转方式:左左旋转、左右旋转、右左旋转、右右旋转。
左左(LL):
左右(LR):
右左(RL):
右右(RR):
二)AVL平衡二叉树
第一步:初始化AVL平衡二叉树结构
/**
* AVL平衡二叉树
* @author ouyangjun
*/
public class AvlTree<T extends Comparable<T>> {
// 初始化一个树结构对象
static class Node<T> {
Node<T> left; // 左子节点
Node<T> right; // 右子节点
T value; // 节点的值
int height; // 高度
Node(Node<T> left, Node<T> right, T value) {
this.left = left;
this.right = right;
this.value = value;
this.height = 0;
}
}
private Node<T> root; // 根节点
public Node<T> getRoot() {
return root;
}
public void setRoot(Node<T> root) {
this.root = root;
}
}
第二步:增加几个获取二叉树高度的辅助方法
/**
* 根据二叉树节点的高度
* @param Node
*/
private int height(Node<T> node) {
if (node != null) {
return node.height;
}
return 0;
}
/**
* 求左子节点和右子节点最大高度差
* @param left
* @param right
* @return
*/
private int maxHeight(Node<T> left, Node<T> right) {
return height(left) > height(right) ? height(left) : height(right);
}
第三步:左左旋转(LL旋转),将k1旋转成根节点,k2变成k1的右孩子,"k1的右孩子"变成"k2的左孩子"。
/**
* 左左旋转
* @param node
* @return
*/
private Node<T> leftleft(Node<T> k2) {
Node<T> k1 = k2.left; // 把k2下的左边节点k1作为根节点
k2.left = k1.right; // 把k1下的右节点Y作为k2的左节点
k1.right = k2; // 把k2作为k1的右节点
// k2高度降低了, k1高度提高了, 重新计算高度
k2.height = maxHeight(k2.left, k2.right) + 1;
k1.height = maxHeight(k1.left, k1.right) + 1;
// 返回
return k1;
}
第四步:右右旋转(RR旋转),将k2旋转成根节点,k1变成k2的左孩子,"k2的左孩子"变成"k1的右孩子"。
/**
* 右右旋转
* @param node
* @return
*/
private Node<T> rightright(Node<T> k1) {
Node<T> k2 = k1.right; // 把根节点k1下的右边节点k2作为根节点
k1.right = k2.left; // 把k2下的左节点Y作为k1的右节点
k2.left = k1; // 把k1作为k2的左节点
// k1高度降低了, k2高度提高了
k1.height = maxHeight(k1.left, k1.right);
k2.height = maxHeight(k2.left, k2.right);
// 返回
return k2;
}
第五步:左右旋转(LR旋转),先右右,再左左。
/**
* 左右旋转,先右右,再左左
* @param node
* @return
*/
private Node<T> leftright(Node<T> node){
//注意传递的参数
node.left = rightright(node.left);
return leftleft(node);
}
第六步:右左旋转(RL旋转),先左左,再右右。
/***
* 右左旋转,先左左,再右右
* @param node
* @return
*/
private Node<T> rightleft(Node<T> node){
node.right = leftleft(node.right);
return rightright(node);
}
第七步:增加一些辅助方法
/**
* 前序遍历:根节点 ==》左子节点 ==》右子节点
* @param node
*/
public void preNode(Node<T> node) {
if (node != null) {
System.out.print(node.value + "\t");
preNode(node.left);
preNode(node.right);
}
}
/**
* 中序遍历:左子节点 ==》根节点 ==》右子节点
* @param node
*/
public void inNode(Node<T> node) {
if (node != null) {
inNode(node.left);
System.out.print(node.value + "\t");
inNode(node.right);
}
}
/**
* 后序遍历:左子节点 ==》右子节点 ==》根节点
* @param node
*/
public void nextNode(Node<T> node) {
if (node != null) {
nextNode(node.left);
nextNode(node.right);
System.out.print(node.value + "\t");
}
}
/**
* (递归实现)查找"AVL树"中键值为value的节点
*/
public Node<T> search(T value) {
return search(getRoot(), value);
}
private Node<T> search(Node<T> node, T value) {
if (node == null) {
return node;
}
int cmp = value.compareTo(node.value);
if (cmp < 0) {
return search(node.left, value);
} else if (cmp > 0) {
return search(node.right, value);
}
return node;
}
/**
* 查找AVL树的最大结点,最大节点肯定是在右边
*/
public T maxNode() {
Node<T> node = maxNode(getRoot());
if (node != null) {
return node.value;
}
return null;
}
private Node<T> maxNode(Node<T> node) {
if (node == null) {
return null;
}
// 循环直到最后一个节点,就是最大的节点
while (node.right != null) {
node = node.right;
}
return node;
}
第八步:新增节点
/**
* 新增节点
* @param value
* @return
*/
public void add(T value) {
setRoot(add(getRoot(), value));
}
private Node<T> add(Node<T> node, T value) {
if (node == null) {
node = new Node<T>(null, null, value); // 创建一个新节点
} else {
// 比较value,如果value小于currentNode.value返回-1, 如果大于则返回1, 相等就返回0
int num = value.compareTo(node.value);
if (num < 0) {
// 将value插入到左边
node.left = add(node.left, value);
// 插入节点后,若AVL树失去平衡,则进行相应的调节。
if (height(node.left) - height(node.right) == 2) {
if (value.compareTo(node.left.value) < 0) {
node = leftleft(node);
} else {
node = leftright(node);
}
}
} else if (num > 0) {
// 将value插入到右边
node.right = add(node.right, value);
// 插入节点后,若AVL树失去平衡,则进行相应的调节。
if (height(node.right) - height(node.left) == 2) {
if (value.compareTo(node.right.value) > 0) {
node = rightright(node);
} else {
node = rightleft(node);
}
}
} else {
System.out.println("添加节点失败: 不允许添加相同的节点!");
}
}
// 重新计算高度
node.height = maxHeight(node.left, node.right) + 1;
// 返回
return node;
}
第九步:删除节点
/**
* 删除节点
* @param value
* @return
*/
public void remove(T value) {
Node<T> node = search(getRoot(), value);
// 判断是否存在value的节点
if (node != null) {
setRoot(remove(getRoot(), node));
}
}
private Node<T> remove(Node<T> rootNode, Node<T> node) {
if (rootNode == null || node == null) {
return null;
}
int num = node.value.compareTo(rootNode.value);
if (num < 0) {
// 在左节点中删除
rootNode.left = remove(rootNode.left, node);
// 删除节点后,若AVL树失去平衡,则进行相应的调节。
if (height(rootNode.right) - height(rootNode.left) == 2) {
Node<T> r = rootNode.right;
if (height(r.left) > height(r.right)) {
rootNode = rightleft(rootNode);
} else {
rootNode = rightright(rootNode);
}
}
} else if (num > 0) {
// 在右节点中删除
rootNode.right = remove(rootNode.right, node);
// 删除节点后,若AVL树失去平衡,则进行相应的调节。
if (height(rootNode.left) - height(rootNode.right) == 2) {
Node<T> l = rootNode.left;
if (height(l.right) > height(l.left)) {
rootNode = leftright(rootNode);
} else {
rootNode = leftleft(rootNode);
}
}
} else {
// 删除节点本身,需要考虑到该节点的左孩子和右孩子是否为空
if ((rootNode.left != null) && (rootNode.right != null)) {
if (height(rootNode.left) > height(rootNode.right)) {
// 如果rootNode的左子树比右子树高,删除"rootNode的左子树中最大节点"之后,AVL树仍然是平衡的。
Node<T> max = maxNode(rootNode.left);
rootNode.value = max.value;
rootNode.left = remove(rootNode.left, max);
} else {
// 如果rootNode的左子树不比右子树高(即它们相等,或右子树比左子树高1),删除"rootNode的右子树中最小节点"之后,AVL树仍然是平衡的。
Node<T> min = maxNode(rootNode.right);
rootNode.value = min.value;
rootNode.right = remove(rootNode.right, min);
}
} else {
// 直接删除
Node<T> temp = rootNode;
rootNode = (rootNode.left != null) ? rootNode.left : rootNode.right;
temp = null;
}
}
return rootNode;
}
第十步:main方法测试
public static void main(String[] args) {
AvlTree<Integer> avlTree = new AvlTree<Integer>();
// 添加节点
avlTree.add(8); // 添加一个根节点8
avlTree.add(15); // 在根节点8下添加一个右节点15
avlTree.add(28); // 在右节点15下面添加一个右节点28, 这个时候会失去平衡, 把15变为根节点,8作为15的左节点,28作为15的右节点。
//avlTree.remove(28); // 还原到上一步,下面的节点都会变化
avlTree.add(4); // 在左节点8下添加一个左节点4
//avlTree.remove(4); // 还原到上一步,下面的节点都会变化
avlTree.add(1); // 在左节点4下添加一个左节点1,这个时候是失去平衡, 原先8的节点位置变为4, 8作为4的右节点, 1作为4的左节点。
avlTree.add(40); // 在右节点28下添加一个右节点40
avlTree.add(58); // 在右节点40下添加一个右节点58,会失去平衡, 原先28的节点位置变为40, 28作为40的左节点, 58作为40的右节点
System.out.println("前序遍历结果为:");
avlTree.preNode(avlTree.getRoot());
System.out.println("\n中序遍历结果为:");
avlTree.inNode(avlTree.getRoot());
System.out.println("\n后序遍历结果为:");
avlTree.nextNode(avlTree.getRoot());
}
打印效果图:
新增节点步骤图解:
第一步:新增节点8,直接作为根节点
第二步:新增节点15,作为根节点8的右节点
第三步:新增节点28,首先找到插入的位置,在节点15下面添加右节点28, 这个时候会失去平衡,需要进行右右旋转。 把节点15变为根节点,节点8作为节点15的左节点,节点28作为节点15的右节点。
第四步:新增节点4,作为节点8的左节点
第五步:新增节点1,首先找到插入的位置,在节点4下添加一个左节点1,这个时候是失去平衡,需要进行左左旋转,节点8变为节点4的右节点,节点4替换原先节点8的位置。
第六步:新增节点40,作为节点28的右节点
第七步:新增节点58,首先找到插入的位置,在节点40下添加一个右节点58,会失去平衡,需进行右右旋转,节点28作为节点40的左节点,节点40替换原先节点28的位置。
第八步:AVL平衡二叉树效果图
识别二维码关注个人微信公众号
本章完结,待续,欢迎转载!
本文说明:该文章属于原创,如需转载,请标明文章转载来源!