AVL树(Java实现)

AVL树基本介绍

AVL树是一种自平衡的二叉查找树,在AVL树中任何节点的两个子树的高度差不能超过1。就是相当于在二叉搜索树的基础上,在插入和删除时进行了平衡处理。

不平衡的四种情况

LL:结构介绍

看如下图,假设最初只有k1, k2, k3, y, z 五个结点,这时该树两边的高度分别为3 和 2,相差为1,满足AVL平衡的概念。

随后插入了结点 x ,导致了不平衡。k1.left.left 有了子树,导致了不平衡。所以是LL结构。

(这个 x 结点是k3的左孩子还是右孩子无所谓,因为无论在左还是在右,处理的方式是通用的)

AVL树(Java实现)  AVL树(Java实现)

LL:处理

调整的时候发现,k3 和 x 的相对连接关系一直没变,所以x是k3的左孩子还是右孩子无所谓了,都是LL处理方式。x 如果是 k3 的左孩子,处理方式和步骤一模一样。

AVL树(Java实现)---> AVL树(Java实现)---> AVL树(Java实现)--->

--->AVL树(Java实现)---> AVL树(Java实现)

LL:代码

    /**
* @param k1 k1结点的左右两边的高度差2
* @return 返回LL单转后的新根k2
*/
private Node leftLeftRotation(Node k1) {
Node k2 = k1.left; k1.left = k2.right;
k2.right = k1; k1.height = max(height(k1.left), height(k1.right)) + 1;
k2.height = max(height(k2.left), k1.height) + 1; return k2;
}  

RR:结构介绍

看如下图,假设最初只有k1, k2, k3, x, y 五个结点,这时该树两边的高度分别为2 和 3,相差为1,满足AVL平衡的概念。

随后插入了结点 z ,导致了不平衡。是 k1.right.right 有了子树,导致了不平衡。所以是RR结构。

(这个 z 结点是k3的左孩子还是右孩子无所谓,因为无论在左还是在右,处理的方式是通用的)

AVL树(Java实现)  AVL树(Java实现)

RR:处理

调整的时候发现,k3 和 z 一直没动,所以 z 是 k3 的左孩子还是右孩子无所谓了,都是 RR 处理方式。z 如果是 k3 的左孩子,处理方式和步骤一模一样。

AVL树(Java实现)--->AVL树(Java实现)--->AVL树(Java实现)--->

--->AVL树(Java实现)--->AVL树(Java实现)

RR:代码

    /**
* @param k1 k1结点的左右两边的高度差2
* @return 返回RR单转后的新根k2
*/
private Node rightRightRotation(Node k1) {
Node k2 = k1.right; k1.right = k2.left;
k2.left = k1; k1.height = max(height(k1.left), height(k1.right)) + 1;
k2.height = max(k1.height, height(k2.right)) + 1; return k2;
}  

LR:结构介绍

看如下图,假设最初只有k1, k2, k3, x, z 五个结点,这时该树两边的高度分别为3 和 2,相差为1,满足AVL平衡的概念。

随后插入了结点 y ,导致了不平衡。是 k1.left.right 有了子树,导致了不平衡。所以是LR结构。

(这个 y 结点是k3的左孩子还是右孩子无所谓,因为无论在左还是在右,处理的方式是通用的)

AVL树(Java实现)  AVL树(Java实现)

LR:处理

为了后面更方便地描述,我把k3的右孩子 null 空指针画了出来。

AVL树(Java实现)

现在是LR结构。

先不看k1 和 z 两个结点,把 k1 和 k2 断开连接。把k2为根的子树当成RR结构来进行处理。即先处理这个'R'

AVL树(Java实现)--->AVL树(Java实现)---> AVL树(Java实现)

如上面最后一张图,经过了RR单转之后,变成了LL结构。对k1为根的树其进行LL单转。如下所示:

AVL树(Java实现)--->AVL树(Java实现)--->AVL树(Java实现)

本次我只演示了当 y 是k3的左孩子时的情况。如果y插入时,是k3的右孩子,那其实就是图片中null结点的地方。转换方式是一模一样。

LR:代码

发现LL 和 RR写好以后,LR虽然复杂,但直接调用这两个方法就行了。很短的。

    /**
* @param k1 k1结点的左右两边的高度差2
* @return 返回LR单转后的新根k2
*/
private Node leftRightRotation(Node k1) {
k1.left = rightRightRotation(k1.left);
return leftLeftRotation(k1);
}  

RL:结构介绍

看如下图,假设最初只有k1, k2, k3, x, z 五个结点,这时该树两边的高度分别为2 和 3,相差为1,满足AVL平衡的概念。

随后插入了结点 y ,导致了不平衡。是 k1.right.left 有了子树,导致了不平衡。所以是RL结构。

(这个 y 结点是k3的左孩子还是右孩子无所谓,因为无论在左还是在右,处理的方式是通用的)

AVL树(Java实现)  AVL树(Java实现)

RL:处理

为了后面更方便地描述,我把k3的右孩子 null 空指针画了出来。

AVL树(Java实现)

现在是RL结构。

先不看k1 和 x 两个结点,把 k1 和 k2 断开连接。把k2为根的子树当成LL结构来进行处理。即先处理这个'L'

AVL树(Java实现)->AVL树(Java实现)->AVL树(Java实现)->AVL树(Java实现)

如上面最后一张图,经过了RR单转之后,变成了RR结构。对k1为根的树其进行RR单转。如下所示:

AVL树(Java实现)  AVL树(Java实现)  AVL树(Java实现)

本次我只演示了当 y 是k3的左孩子时的情况。如果y插入时,是k3的右孩子,那其实就是图片中null结点的地方。转换方式是一模一样。

RL:代码

    /**
* @param k1 k1结点的左右两边的高度差2
* @return 返回RL单转后的新根k2
*/
private Node rightLeftRotation(Node k1) {
k1.right = leftLeftRotation(k1.right);
return rightRightRotation(k1);
}  

具体讲解插入导致的不平衡

咱们假设AVL树的初始状态如下,

AVL树(Java实现)

随后插入了一个新的结点7。7与根进行比较,发现7<50,所以去左子树查找。

AVL树(Java实现)

7继续进行比较,7<30,所以继续去左子树查找。

AVL树(Java实现)

重复这些步骤,在代码里是用递归处理的。直到找到7为止,或者遍历到叶子节点了也没找到7为止。

AVL树(Java实现)

没找到7...因为7比15小,所以7插入在15左侧。这时以k1为根的树不平衡了!!!

AVL树(Java实现)

问:我能用眼睛看出来,但程序怎么算呢?   答:height(k1.left) - height(k1.right) == 2 了。

问:为什么是左子树高度 减 右子树高度?   答:因为...之前是平衡的,在左侧插入后就不平衡了。肯定是左侧大啊。

问:嗯,左子树高度比右子树大说明啥?   答:说明...肯定是 k1 左边。也就是LL结构或者是LR结构,第一个字母 ‘L’ 已经确定了。

问:那剩下的你怎么确定是LL还是LR ?  答:很简单,新节点比k2小,那就是LL;新节点比k2大,那就是LR。继续往下看图:

AVL树(Java实现)---确定了LL结构--->AVL树(Java实现)

问:LL结构挺好理解,判断LR会不会很麻烦啊? 答:方法一模一样...没多没少...咱们假设一开始插入的不是7,而是35,那么如下图所示:

AVL树(Java实现)---确定LR--->AVL树(Java实现)

问:为什么不介绍RR和RL了? 答:LL、LR都会了,RR、RL其实就是一码事了。不再重复演示了.....(画图好累....)

具体讲解删除导致的不平衡

讲删除导致不平衡之前,先讲讲到底怎么在AVL树中删除一个结点。

假设删除前的树如下图所示,想要删除 90 结点:

AVL树(Java实现)

问:怎么删除呢? 答:先从树根定位到 90 结点。

问:怎么定位呢?  答:利用递归,与当前子树根进行比较。小,去左边找;大,去右边找;等于,就找到了。

AVL树(Java实现)

问:找到之后就可以直接删了吗? 答:还得考虑要删除的那个结点是否还有孩子。分为如下三种情况:

情况1:   90结点没有左孩子

没有左孩子的话,把70结点的right 连上 90结点的right。没有左孩子不代表一定有右孩子。如果90的right是null的话,那就把这个null赋给70的right就好了。

AVL树(Java实现)->AVL树(Java实现)->AVL树(Java实现)

情况2:   90结点没有右孩子

没有右孩子的话,把 70结点的right 连上 90结点的left。

AVL树(Java实现)->AVL树(Java实现)->AVL树(Java实现)

情况3:   90结点既有左孩子又有右孩子

如果要删除的 90 结点情况如下,那么需要在删除前找到 90 结点的 前驱结点 或者 后继结点。如下面的第一张图。

问:找 前驱结点 / 后继结点 干嘛呢? 答:90的前驱和后继结点是最接近90的两个结点。用他们俩替换掉90,就相当于删掉了90。如下面的第二张图。

问:第二张图我看完了,你选用90的后继结点93替换了90,之后树中不就有两个93了吗? 答:删掉原来的93就好了

问:原来的93怎么删? 答:去新的93的右子树里找原来的93,并且删除。咱们现在介绍的是情况3(被删除的结点有左右孩子),而且93是后继结点,肯定不会有左孩子,所以删除原来的93的问题,就回到了情况1。

一句概括就是:在新93结点的右子树中删除原93。(见下面第3张图)

问:你图中演示的是后继结点93来替换90,那前驱结点呢? 答:刚才说了后继结点肯定没有左孩子,而前驱结点肯定没有右孩子。用前驱80结点来替换90之后,再去删除原来的80,而这个80肯定没有右孩子,所以就回到了情况2。

一句话概括就是:如果用前驱80替换90,再删除原来的80,就回到了情况2;如果用后继93替换90,再删除原来的93,就回到了情况1。

看起来选前驱和选后继没什么两样,但是,如果90结点的右子树深,最好用后继来替换;如果90结点的左子树深,最好用前驱来替换。

AVL树(Java实现)->AVL树(Java实现)->AVL树(Java实现)

问:前驱和后继怎么求呢? 答: 首先要会求树中的最大、最小结点。从根开始向左遍历,到头了,那么这个最左边的叶子节点就是最小值。同理,一直向右遍历就是最大值。

问:那么最大最小值跟前驱后继什么关系呢? 答:咱们以情况3的第一张图片为例。找50的前驱怎么找呢,就是50结点的左子树中的最大值。后继结点呢,就是50结点的右子树中的最小值。

问:还是不懂,给我看看代码?

答:

    private Node minNode(Node node) {
if (node == null) {
return null;
} else if (node.left == null) {
return node;
} else {
return minNode(node.left);
}
}
--------------------------------------------------------
Node successor = minNode(node.right);//找node的后继结点successor

  

如何删除一个结点已经讲完了。

下面看看删除一个结点后都会遇到什么不平衡的情况。

假设删除前的树如下图所示,想要删除 90 结点:

AVL树(Java实现)

删除值为90的结点后,高度差为2,不满足AVL定义。

AVL树(Java实现)

因为是删除了50 (k1) 右子树中的结点后导致的不平衡,所以肯定是左子树太深导致了不平衡。

第一个字母‘L’已经确定,所以是 LL 和 LR 结构之一。

需要判断 k2 的左右两子树,左边深,那就是LL;右边深,那就是LR。

AVL树(Java实现)---确定LL结构--->AVL树(Java实现)

如果k2的左右两子树高度相等...那就LL/LR随意了...主要目的就是处理掉高的那部分。

AVL树完整代码

public class AVLTree<Key extends Comparable<? super Key>, Value> {
private class Node {
Key key;//键,相当于词典里的单词
Value value;//值,相当于词典里的单词解释
int height;//结点的高度
Node left;
Node right; public Node(Key key, Value value) {
this.key = key;
this.value = value;
this.left = null;
this.right = null;
int height = 1;
}
} private Node root; public AVLTree() {
root = null;
} private int height(Node node) {
if (node != null) {
return node.height;
}
return 0;
} public int height() {
return height(root);
} private int max(int a, int b) {
return a > b ? a : b;
} private void replaceNode(Node src, Node tar) {
tar.key = src.key;
tar.value = src.value;
} private void preOrder(Node node) {
if (node != null) {
System.out.println(node.key);
preOrder(node.left);
preOrder(node.right);
}
} public void preOrder() {
preOrder(root);
} private void inOrder(Node node) {
if (node != null) {
inOrder(node.left);
System.out.println(node.key);
inOrder(node.right);
}
} public void inOrder() {
inOrder(root);
} public void postOrder(Node node) {
if (node != null) {
postOrder(node.left);
postOrder(node.right);
System.out.println(node.key);
}
} public void postOrder() {
postOrder(root);
} private Node search(Node node, Key key) {
if (node == null) {
return null;
} else if (key.compareTo(node.key) == 0) {
return node;
} else if (key.compareTo(node.key) < 0) {
return search(node.left, key);
} else {//key.compareTo(node.key) > 0
return search(node.right, key);
}
} public Node search(Key key) {
return search(root, key);
} private Node minNode(Node node) {
if (node == null) {
return null;
} else if (node.left == null) {
return node;
} else {
return minNode(node.left);
}
} public Node minNode() {
return minNode(root);
} private Node maxNode(Node node) {
if (node == null) {
return null;
} else if (node.right == null) {
return node;
} else {
return maxNode(node.right);
}
} public Node maxNode() {
return maxNode(root);
} // 对如下的LL情况
//
// k1 k2
// / \ / \
// k2 z LL单转 x k1
// / \ ----\ / / \
// x y ----/ o y z
// // / k1右旋
// o
//
// 或
//
// k1 k2
// / \ / \
// k2 z LL单转 x k1
// / \ ----\ \ / \
// x y ----/ o y z
// \ k1右旋
// o
//
private Node leftLeftRotation(Node k1) {
Node k2 = k1.left; //k2是k1的左子树 k1.left = k2.right;//k2的右子树 变为 k1 的左子树
k2.right = k1; //k1变为k2的右子树 k1.height = max(height(k1.left), height(k1.right)) + 1;//计算k1的高度
k2.height = max(height(k2.left), k1.height) + 1;//计算k2的高度 return k2;//返回新的根k2
} // 对如下的RR情况
//
// k1 k2
// / \ / \
// x k2 RR单转 k1 k3
// / \ ----\ / \ \
// y k3 ----/ x y z
// \ k1左旋
// z
//
// 或
//
// k1 k2
// / \ / \
// x k2 RR单转 k1 k3
// / \ ----\ / \ /
// y k3 ----/ x y z
// / k1左旋
// z
//
public Node rightRightRotation(Node k1) {
Node k2 = k1.right; k1.right = k2.left;
k2.left = k1; k1.height = max(height(k1.left), height(k1.right)) + 1;
k2.height = max(k1.height, height(k2.right)) + 1; return k2;
} // 对如下的LR情况
// k1 k1 k3
// / \ / \ / \
// k2 z RR单转 k3 z LL单转 k2 k1
// / \ -----\ / \ -----\ / \ / \
// w k3 -----/ k2 y -----/ w x y z
// / \ k2左旋 / \ k1右旋
// x y w x
//
public Node leftRightRotation(Node k1) {
k1.left = rightRightRotation(k1.left);
return leftLeftRotation(k1);
} // 对如下的RL情况
// k1 k1 k3
// / \ LL单转 / \ RR单旋 / \
// w k2 -----\ w k3 -----\ k1 k2
// / \ -----/ / \ -----/ / \ / \
// k3 z k2右旋 x k2 k1左旋 w x y z
// / \ / \
// x y y z
//
public Node rightLeftRotation(Node k1) {
k1.right = leftLeftRotation(k1.right);
return rightRightRotation(k1);
} //插入
private Node insert(Node node, Key key, Value value) {
if (node == null) return new Node(key, value); if (key.compareTo(node.key) == 0) {//如果key相同则更新该节点 node.value = value; } else if (key.compareTo(node.key) < 0) {//如果key比当前根小,则去左子树找。即一步Left node.left = insert(node.left, key, value);
if (height(node.left) - height(node.right) == 2) {//插在左边所以肯定是左-右,高度差2表示已经不平衡
if (key.compareTo(node.left.key) < 0) {// 又一步Left,所以是LeftLeft
node = leftLeftRotation(node);
} else { //一步Right,所以是LeftRight
node = leftRightRotation(node);
}
} } else { // node.key < key,那么去右子树找.即一步Right node.right = insert(node.right, key, value);
if (height(node.right) - height(node.left) == 2) {//插在右边所以肯定是右-左,高度差2表示已经不平衡
if (key.compareTo(node.right.key) > 0) {//又一步Right,所以是RightRight
node = rightRightRotation(node);
} else {//一步Left,所以是RightLeft
node = rightLeftRotation(node);
}
} } node.height = max(height(node.left), height(node.right)) + 1;
return node;
} public void insert(Key key, Value value) {
this.root = insert(this.root, key, value);
} //删除 /**
* @param node 当前子树根节点
* @param target 要删除的结点
* @return 删除后的新的子树根
*/
public Node remove(Node node, Node target) {
if (node == null || target == null) return node; if (target.key.compareTo(node.key) < 0) {//待删除key的比根的key小,那么继续在左子树查找 node.left = remove(node.left, target);
if (height(node.right) - height(node.left) == 2) {//如果在删除后失去平衡
if (height(node.right.left) <= height(node.right.right)) {
node = rightRightRotation(node);
} else {
node = rightLeftRotation(node);
}
} } else if (node.key.compareTo(target.key) < 0) {//待删除key的比根的key大,那么继续在右子树查找 node.right = remove(node.right, target);
if (height(node.left) - height(node.right) == 2) {
if (height(node.left.right) <= height(node.left.left)) {
node = leftLeftRotation(node);
} else {
node = rightRightRotation(node);
}
} } else { // node.key == target.key
if (node.left == null) { // 如果node的左子树为空,那么删除node后,新的根就是node.right
return node.right;
} else if (node.right == null) {// 如果node的右子树为空,那么删除node后,新的根就是node.left
return node.left;
} else { // 如果node既有左子树,又有右子树 if (height(node.left) > height(node.right)) {//如果左子树比右子树深
Node predecessor = maxNode(node.left);//找node的前继结点predecessor
replaceNode(predecessor, node);//predecessor替换node
node.left = remove(node.left, predecessor);//再把原来的predecessor删掉
} else {//如果右子树比左子树深(一样深的话无所谓了)
Node successor = minNode(node.right);//找node的后继结点successor
replaceNode(successor, node);//successor替换node
node.right = remove(node.right, successor);//再把原来的successor删掉
} }
}
return node;
} public void remove(Key key) {
Node z;
if ((z = search(root, key)) != null)
root = remove(root, z);
} private void destroy(Node node) {
if (node == null)
return; if (node.left != null)
destroy(node.left);
if (node.right != null)
destroy(node.right); node = null;
} public void destroy() {
destroy(root);
System.out.println("销毁完毕");
} private void print(Node tree, Key key, String pos) {
if (tree != null) {
if (pos.equals("")) // tree是根节点
System.out.printf("%2d is root\n", tree.key);
else // tree是分支节点
System.out.printf("%2d is %2d's %6s child\n", tree.key, key, pos); print(tree.left, tree.key, "left");
print(tree.right, tree.key, "right");
}
} public void print() {
if (root != null) print(root, root.key, "");
} //***************************************************************
private static int arr[] = {3, 2, 1, 4, 5, 6, 7, 16, 15, 14, 13, 12, 11, 10, 8, 9}; public static void main(String[] args) {
int i;
AVLTree<Integer, Integer> tree = new AVLTree<>(); System.out.printf("*******依次添加: ");
for (i = 0; i < arr.length; i++) {
System.out.printf("%d ", arr[i]);
tree.insert(arr[i], arr[i]);
}
System.out.println(); System.out.print("*******前序遍历: ");
tree.preOrder();
System.out.println(); System.out.print("*******中序遍历: ");
tree.inOrder();
System.out.println(); System.out.print("*******后序遍历: ");
tree.postOrder();
System.out.println(); System.out.println("*******高度:" + tree.height());
System.out.println("*******最小值:" + tree.minNode().key);
System.out.println("*******最大值:" + tree.maxNode().key);
System.out.println("*******树的详细信息:");
tree.print();
System.out.println(); i = 8;
System.out.printf("*******删除根节点: %d", i);
tree.remove(i);
System.out.println(); System.out.println("*******高度:" + tree.height());
System.out.println("*******中序遍历: ");
tree.inOrder();
System.out.println(); System.out.println("*******树的详细信息:");
tree.print();
System.out.println(); // 销毁二叉树
tree.destroy();
}
}

  

上一篇:系统管理模块_岗位管理_改进_使用ModelDroven方案_套用美工写好的页面效果_添加功能与修改功能使用同一个页面


下一篇:jQuery自定义多选下拉框