AVL树(Java实现)
AVL树基本介绍
AVL树是一种自平衡的二叉查找树,在AVL树中任何节点的两个子树的高度差不能超过1。就是相当于在二叉搜索树的基础上,在插入和删除时进行了平衡处理。
不平衡的四种情况
LL:结构介绍
看如下图,假设最初只有k1, k2, k3, y, z 五个结点,这时该树两边的高度分别为3 和 2,相差为1,满足AVL平衡的概念。
随后插入了结点 x ,导致了不平衡。k1.left.left 有了子树,导致了不平衡。所以是LL结构。
(这个 x 结点是k3的左孩子还是右孩子无所谓,因为无论在左还是在右,处理的方式是通用的)
LL:处理
调整的时候发现,k3 和 x 的相对连接关系一直没变,所以x是k3的左孩子还是右孩子无所谓了,都是LL处理方式。x 如果是 k3 的左孩子,处理方式和步骤一模一样。
--->
--->
--->
--->--->
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的左孩子还是右孩子无所谓,因为无论在左还是在右,处理的方式是通用的)
RR:处理
调整的时候发现,k3 和 z 一直没动,所以 z 是 k3 的左孩子还是右孩子无所谓了,都是 RR 处理方式。z 如果是 k3 的左孩子,处理方式和步骤一模一样。
--->
--->
--->
--->--->
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的左孩子还是右孩子无所谓,因为无论在左还是在右,处理的方式是通用的)
LR:处理
为了后面更方便地描述,我把k3的右孩子 null 空指针画了出来。
现在是LR结构。
先不看k1 和 z 两个结点,把 k1 和 k2 断开连接。把k2为根的子树当成RR结构来进行处理。即先处理这个'R'
--->
--->
如上面最后一张图,经过了RR单转之后,变成了LL结构。对k1为根的树其进行LL单转。如下所示:
--->
--->
本次我只演示了当 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的左孩子还是右孩子无所谓,因为无论在左还是在右,处理的方式是通用的)
RL:处理
为了后面更方便地描述,我把k3的右孩子 null 空指针画了出来。
现在是RL结构。
先不看k1 和 x 两个结点,把 k1 和 k2 断开连接。把k2为根的子树当成LL结构来进行处理。即先处理这个'L'
->
->
->
如上面最后一张图,经过了RR单转之后,变成了RR结构。对k1为根的树其进行RR单转。如下所示:
本次我只演示了当 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树的初始状态如下,
随后插入了一个新的结点7。7与根进行比较,发现7<50,所以去左子树查找。
7继续进行比较,7<30,所以继续去左子树查找。
重复这些步骤,在代码里是用递归处理的。直到找到7为止,或者遍历到叶子节点了也没找到7为止。
没找到7...因为7比15小,所以7插入在15左侧。这时以k1为根的树不平衡了!!!
问:我能用眼睛看出来,但程序怎么算呢? 答:height(k1.left) - height(k1.right) == 2 了。
问:为什么是左子树高度 减 右子树高度? 答:因为...之前是平衡的,在左侧插入后就不平衡了。肯定是左侧大啊。
问:嗯,左子树高度比右子树大说明啥? 答:说明...肯定是 k1 左边。也就是LL结构或者是LR结构,第一个字母 ‘L’ 已经确定了。
问:那剩下的你怎么确定是LL还是LR ? 答:很简单,新节点比k2小,那就是LL;新节点比k2大,那就是LR。继续往下看图:
---确定了LL结构--->
问:LL结构挺好理解,判断LR会不会很麻烦啊? 答:方法一模一样...没多没少...咱们假设一开始插入的不是7,而是35,那么如下图所示:
---确定LR--->
问:为什么不介绍RR和RL了? 答:LL、LR都会了,RR、RL其实就是一码事了。不再重复演示了.....(画图好累....)
具体讲解删除导致的不平衡
讲删除导致不平衡之前,先讲讲到底怎么在AVL树中删除一个结点。
假设删除前的树如下图所示,想要删除 90 结点:
问:怎么删除呢? 答:先从树根定位到 90 结点。
问:怎么定位呢? 答:利用递归,与当前子树根进行比较。小,去左边找;大,去右边找;等于,就找到了。
问:找到之后就可以直接删了吗? 答:还得考虑要删除的那个结点是否还有孩子。分为如下三种情况:
情况1: 90结点没有左孩子
没有左孩子的话,把70结点的right 连上 90结点的right。没有左孩子不代表一定有右孩子。如果90的right是null的话,那就把这个null赋给70的right就好了。
->
->
情况2: 90结点没有右孩子
没有右孩子的话,把 70结点的right 连上 90结点的left。
->
->
情况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结点的左子树深,最好用前驱来替换。
->
->
问:前驱和后继怎么求呢? 答: 首先要会求树中的最大、最小结点。从根开始向左遍历,到头了,那么这个最左边的叶子节点就是最小值。同理,一直向右遍历就是最大值。
问:那么最大最小值跟前驱后继什么关系呢? 答:咱们以情况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 结点:
删除值为90的结点后,高度差为2,不满足AVL定义。
因为是删除了50 (k1) 右子树中的结点后导致的不平衡,所以肯定是左子树太深导致了不平衡。
第一个字母‘L’已经确定,所以是 LL 和 LR 结构之一。
需要判断 k2 的左右两子树,左边深,那就是LL;右边深,那就是LR。
---确定LL结构--->
如果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(); } }
学如不及,犹恐失之