定义

一棵AVL树可以是空树,
也可以是具有下列性质的二叉搜索树:它的左子树和右子树都是AVL树,且左子树和右子树的高度之差的绝对值不超过1。(右子树高度-左子树高度)

结点的平衡因子:给每个结点附加一个数字,给出该结点右子树的高度减去做技术的高度所得的高度差,这个数字即为结点的平衡因子。

如果一棵二叉搜索树的高度是平衡的,它就成为AVL树,如果它有n个节点,其高度可保持在O(以2为底的logn)

平衡化旋转
① 如果在一棵平衡的二叉搜索树欧中插入一个新结点,造成了不平衡。此时必须调整树的结构,使之平衡化。
平衡化旋转有两类:
单旋转(左旋和右旋)
双旋转(左平衡和右平衡)

每插入一个新结点时,AVL树中相关的平衡状态会发生改变,因此,在插入一个新结点后,需要从插入的位置沿向根的路径回溯,检查各节点的平衡因子。
如果在某一节点发现高度不平衡,停止回溯
从发生不平衡的结点起,沿刚才回溯的路径直接下两层的结点
如果这三个结点处于一条直线上,则采用单旋进行平衡化。单旋转可按其方向分为左单旋转和右单旋转,其中一个是另一个的镜像,其方向与不平衡的形状有关。
如果这三个结点处于一条折线上,则采用双旋转进行平衡化。双旋转分为先左后右和先右后左两类。

左单旋转

image.png

  1. // 左单旋转
  2. private void RotateLeft(AVLNode ptr){
  3. AVLNode newRoot = ptr.rightChild; // 1
  4. newRoot.parent = ptr.parent; // 4 维护newroot的双亲
  5. zz
  6. if (newRoot.leftChild!=null) { 维护newroot的左孩子的双亲
  7. newRoot.leftChild.parent = ptr; // 5
  8. }
  9. newRoot.leftChild = ptr; // 3
  10. AVLNode pa = ptr.parent;
  11. if (pa==null){ // 判断ptr的双亲是否为空,有可能ptr以前是根节点。也有可能不是。
  12. root = newRoot;
  13. }else{
  14. if (pa.leftChild==null){
  15. pa.leftChild = newRoot;
  16. }else{
  17. pa.rightChild = newRoot;
  18. }
  19. }
  20. ptr.parent = newRoot; //维护ptr的双亲。
  21. }

右单旋转

image.png

  1. // 右单旋转
  2. private void RotateRight(AVLNode ptr){
  3. AVLNode newroot = ptr.leftChild; //1
  4. newroot.parent = ptr.parent; // 4 维护newroot的双亲
  5. ptr.leftChild = newroot.rightChild; // 2
  6. if (newroot.rightChild!=null){ // 5 维护newroot右孩子的双亲
  7. newroot.rightChild.parent = ptr;
  8. }
  9. newroot.rightChild = ptr; // 3
  10. AVLNode pa =ptr.parent;// 4
  11. if (ptr.parent==null){
  12. root = newroot;
  13. }else{
  14. if (pa.leftChild==ptr){
  15. pa.leftChild = newroot;
  16. }
  17. if (pa.rightChild==ptr){
  18. pa.rightChild =newroot;
  19. }
  20. }
  21. ptr.parent = newroot; //6 维护ptr的双亲
  22. }

双旋转

若某一个节点的平衡因子发生改变,沿着回溯路径向下两层,以最后一层的节点为旋转点,进行先左单旋后右单旋。 或者进行先右单后左单旋。
现有一棵AVL树,每个节点都平衡因子的绝对值 都没有超过2,所以该AVL树是平衡的。
image.png

先左后右双旋转

现在我们可以往 1 2 3这三个位置插入节点。
情况一:
若我们向1位置插入节点。此时就变成了:
image.png
此时,我们插入的情况可能有两种,一种是a位置插入,一种是在b位置插入。此时 节点B的平衡因子变为-1,节点A的平衡因子变为-2.AVL树不平衡,需要旋转。
我们从新插入的节点开始,向上回溯至不平衡节点的位置,找到A结点,于是按照旋转规则,找到刚才回溯路径的包括A节点在内的3个节点。即A B C 我们开始进行旋转。所以,如果我们在1处插入节点,(无论是插到a还是b上)那我们需要旋转的条件就是,当B的平衡因子变为-1时,就需要进行单旋转。在进行完单旋转后,我们还需要将B的平衡因子重新赋值。
即进行单旋转后,B的右孩子指向A,A的左孩子指向B
image.png
通过图得,我们B的平衡因子置为0。A的平衡因子为0.

情况二:
当我们是新插入的节点是在B节点的右孩子插入,即在E的左孩子处插入,即我们图中2位置。

image.png
此时,B的平衡因子变为1,E的平衡因子又变为-1。(这里要注意,如果在E的右孩子插入,则E的平衡因子变为1,所以这里又要分情况讨论。)

image.png

image.png

我们先看E的平衡因子为-1的情况:
从新插入的节点D处开始向上查找 不平衡的节点,找到了A,于是按照遍历规则,A及其下两层的节点 开始旋转。由于 ABE 不在一条直线上,所以需要进行双旋转。
我们先以E为旋转点,EB进行左单旋转。
image.png
此时 B的平衡因子为0,再以E为旋转点,对EA进行右单旋。
image.png此时 E的平衡因为0,A的平衡因子为1

情况三:
当我们新插入的节点,在B的右孩子插入,即在E的右孩子处插入时,即我们3号位置时:
image.png我们通过回溯 查找到A节点的平衡因子为-2,不平衡,于是 找到ABE 不在一条直线上,使用双旋转。
image.png
我们以E为旋转点,对EB进行左单旋转。
image.png
再以E为旋转点,EA进行右旋转。
image.png
此时A的平衡因子为 0 E的平衡因子为0 B的平衡因子为-1。

具体代码实现:

  1. // 左平衡
  2. private void LeftBalance(AVLNode ptr){
  3. AVLNode leftSub = ptr.leftChild;
  4. AVLNode rightSub = null;
  5. switch(leftSub.balance){
  6. case 0:
  7. System.out.println("left balance");
  8. break;
  9. case -1:
  10. ptr.balance = 0;
  11. leftSub.balance = 0;
  12. RotateRight(ptr);
  13. break;
  14. case 1:
  15. rightSub = leftSub.rightChild;
  16. switch(rightSub.balance){
  17. case -1:
  18. ptr.balance = 1;
  19. leftSub.balance = 0;
  20. case 1:
  21. ptr.balance = 0;
  22. leftSub.balance = -1;
  23. case 0:
  24. // 特殊情况
  25. ptr.balance = 0;
  26. leftSub.balance = 0;
  27. }
  28. rightSub.balance = 0;
  29. RotateLeft(leftSub);
  30. RotateRight(ptr);
  31. break;
  32. }
  33. }

先右后左双旋转

是先左后右旋转的镜像。

AVL树的插入

  1. //插入
  2. public boolean insert(int val){
  3. AVLNode p = new AVLNode(val);
  4. adjust(p);
  5. return true;
  6. }
  7. // ptr指向插入的节点
  8. private void adjust(AVLNode ptr){
  9. AVLNode pa = ptr.parent;
  10. boolean high = true;
  11. // 若pa==null,说明pa指向根节点。
  12. while(high&&pa!=null){
  13. if (pa.leftChild == ptr){
  14. // 说明 ptr插入到pa的左边
  15. // 看左边的平衡因子
  16. switch(pa.balance){
  17. case 0:
  18. pa.balance = -1;
  19. break;
  20. case 1:
  21. pa.balance = 0;
  22. high = false;
  23. break;
  24. //pa.balance为-1时,说明原来左高右低
  25. // 此时ptr插入的也是左边,导致左边不平衡,我们就要调用左平衡
  26. case -1:
  27. LeftBalance(pa);
  28. high = false;
  29. break;
  30. }
  31. }else{
  32. // 新添加的节点,在右边插入
  33. switch (pa.balance){
  34. case 0:
  35. pa.balance = 1;
  36. break;
  37. //pa.balance如果为-1,说明pa所指的这棵AVL 左高右低
  38. // 此时ptr插入的是右边,刚好使AVL树平衡
  39. case -1:
  40. pa.balance = 0;
  41. high = false;
  42. break;
  43. //pa.balance如果是1,说明原本pa所指的这棵AVL树 为 右高左低
  44. // 此时 ptr插入右边,导致pa所指的AVL树 右高左低,所以调用右平衡
  45. case 1:
  46. rightBalance(pa);
  47. high = false;
  48. break;
  49. }
  50. }
  51. ptr = pa;
  52. pa = ptr.parent;
  53. }
  54. }