[toc]

AVL平衡二叉树

给你一个数列{1,2,3,4,5,6},要求创建一颗二叉排序树(BST), 问题所在:

  1. 左子树全部为空,从形式上看,更像一个单链表.
  2. 插入速度没有影响
  3. 查询速度明显降低(因为需要依次比较), 不能发挥 BST的优势,因为每次还需要比较左子树,其查询速度比单链表还慢

解决方案–平衡二叉树(AVL)

1. 基本介绍

平衡二叉树也叫平衡 二叉搜索树(**Self-balancing binary search tree**)又被称为 AVL 树, 可以保证查询效率较高。

具有以下特点:

  1. 它是一 棵空树或 它的左右两个子树的高度差的绝对值不超过 1,并且 左右两个子树都是一棵平衡二叉树。平衡二叉树的常用实现方法有红黑树AVL替罪羊树、**Treap伸展树**等。
  2. 举例说明, 看看下面哪些 AVL 树, 为什么?
  3. image-20200324200637728

2. 准备过程

平衡二叉树,与二叉排序树基本思想是相同的,不同的是通过二叉树的左旋和右旋使二叉树根节点左右子树尽量保持一致的一个二叉树。

创建**Node类和AVLTree**类
  • **public int height();**以该节点为根节点的树的高度
  • **public int leftHeight();**返回左子树的高度
  • public int rightHeight(); 返回右子树的高度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
/**
* 平衡二叉树AVL
* @author DuanChaojie
* @date 2020年3月24日 下午5:17:00
* @version 1.0
*/
class AVLTree{

//直接使用BST二叉排序树来

}

/**
* 创建Node节点--直接使用BST的
* @author DuanChaojie
* @date 2020年3月23日 上午11:13:35
* @version 1.0
*/
class Node{

int value;

Node left;

Node right;

//构造器
public Node(int value) {
this.value = value;
}

// toString方法
@Override
public String toString() {
return "Node [value=" + value + "]";
}

/**
* 添加基地那的方法
* 递归的形式添加节点,注意满足二叉排序树的需求
* @param node
*/
public void add(Node node) {
//省略
}

/**
* 中序遍历BST
*/
public void infixOrder() {
//省略
}

/**
*
* @param value 希望删除的节点的值
* @return 如果找到返回该节点,否则返回为null
*/
public Node search(int value) {
//省略
}

/**
*
* @param value 要找到的值得节点
* @return 返回的是要删除的节点的父节点,如果没有就返回null
*/
public Node searchParent(int value) {
//省略
}


/**
* @return 以该节点为根节点的树的高度
*/
public int height() {
return Math.max(left == null ?0:left.height(),right == null?0:right.height())+1;

}

/**
* @return 返回的是左子树的高度
*/
public int leftHeight() {
if(left == null) {
return 0;
}
return left.height();
}

/**
* 返回右子树的高度
*/
public int rightHeight() {
if(right == null) {
return 0;
}
return right.height();
}

}

3. 单旋转(左旋转)

给你一个数列,创建出对应的平衡二叉树.数列 {4,3,6,5,7,8}

image-20200324201721071

代码实现

在Node类中添加该方法,同时在add()方法中通过**rightHeight() - leftHeight() > 1**,进行左旋转调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 左旋转
*/
public void leftRotate() {
//1. 创建新的节点,以当前根节点(root.value)的值
Node newNode = new Node(this.value);

//2. 把新的节点的左子树设置成为root的左子树
newNode.left = this.left;

//3. 把新节点的右子树设置成root的右子树的左子树
newNode.right = this.right.left;

//4.把root节点的值,替换成root.right.value
this.value = this.right.value;

//5. 把当前节点的右子树设置成root 节点的右子树的右子树
this.right = this.right.right;

//把root节点的左子树设置成新的节点
this.left = newNode;

}

4. 单旋转(右旋转)

给你一个数列,创建出对应的平衡二叉树.数列 {10,12, 8, 9, 7, 6}

image-20200324202053214

代码实现

在Node类中添加该方法,同时在add()方法中通过**leftHeight() - rightHeight() > 1**,进行右旋转调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 右旋转
*/
public void rightRotate() {
//1.
Node newNode = new Node(this.value);

//2.
newNode.right = this.right;

//3.
newNode.left = this.left.right;

//4.
this.value = this.left.value;

//5.
this.left = this.left.left;

//6.
this.right = newNode;

}

5. 双旋转

  • 前面的两个数列,进行单旋转(即一次旋转)就可以将非平衡二叉树转成平衡二叉树,但是在某些情况下,单旋转
    不能完成平衡二叉树的转换。
  • 比如数列
    • int[] arr = { 10, 11, 7, 6, 8, 9 }; 运行原来的代码可以看到,并没有转成 AVL 树.
    • int[] arr = {2,1,6,5,7,3}; 运行原来的代码可以看到,并没有转成 AVL 树

image-20200324202338416

解决方案
  1. 当符号右旋转的条件时
  2. 如果它的左子树的右子树高度大于它的左子树的高度
  3. 先对当前这个结点的左节点进行左旋转
  4. 在对当前结点进行右旋转的操作即可
  5. 在Node类add()中修改成一下代码就OK,就是最后两个if判断。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/**
* 添加基地那的方法
* 递归的形式添加节点,注意满足二叉排序树的需求
* @param node
*/
public void add(Node node) {

if(node == null) {
return;
}

//判断传入的节点的值,和当前子树的根节点的值得关系
if(node.value < this.value) {
//如果当前节点左子节点为null
if(this.left == null) {
this.left = node;
}else {
//递归向左子树添加
this.left.add(node);
}
//添加的节点的值大于等于当前节点的值
}else {
if(this.right == null) {
this.right = node;
}else {
//递归向右子树添加
this.right.add(node);
}

}


//当添加完一个节点后,如果rightHeight() - leftHeight() > 1,进行左旋转
if(this.rightHeight() - this.leftHeight() > 1) {
if(this.right != null && this.right.leftHeight() > this.right.rightHeight()) {
this.right.rightRotate();
leftRotate();
}else {
leftRotate();
}

}


if(this.leftHeight() - rightHeight() >1) {
if(this.left != null && this.left.leftHeight() < this.left.rightHeight()) {
this.left.leftRotate();
rightRotate();
}else {
rightRotate();
}

}


}