二叉查找树与AVL树

  1. 1. 4.3 二叉查找树
    1. 1.1. 4.3.0 节点元素的比较实现
    2. 1.2. 4.3.1 contains方法
    3. 1.3. 4.3.2 findMin与findMax方法
    4. 1.4. 4.3.3 insert方法
    5. 1.5. 4.3.4 remove方法
    6. 1.6. 4.3.5 平均查找深度
  2. 2. 4.4 AVL树
    1. 2.1. 4.4.1 单旋转
      1. 2.1.1. 4.4.1.1 左左情况(右旋)
      2. 2.1.2. 4.4.1.12右右情况(左旋)
    2. 2.2. 4.4.2 双旋转

《数据结构与算法分析Java语言描述》第二章4.3与4.4节读书笔记

4.3 二叉查找树

树的节点存储数据,一棵非空树要成为查找树,则对于任意节点x有:x的左子树都小于它,右子树节点都大于它。
二叉查找树的平均深度是O(logN)O(logN),证明可网上查询。可以使用递归实现,不必担心栈溢出。或见4.3.5小节

如下是一个二叉查找树的实现,其中BinaryNode是一个内部类,用来实现节点。

二叉查找树主代码实现(点击展开)
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
/**
* 创建一棵二叉查找树
*
* @date 2021/10/23
**/
public class BinarySearchTree<AnyType> {

private BinaryNode<AnyType> root;
// 比较器
private Comparator cmp;

public BinarySearchTree() {
this.root = null;
}

/**
* 带比较器的构造方法
*
* @param c 构造器传入的比较器
*/
public BinarySearchTree(Comparator<? super AnyType> c) {
root = null;
cmp = c;
}

// 进行比较
private int myCompare(AnyType lhs, AnyType rhs) {
if (cmp != null) {
// 如果传入了比较器,则使用传入的比较器进行比较
return cmp.compare(lhs, rhs);
} else {
// 如果没用,则强制转为Compareable, 如果报错,说明不可转,则也应该直接抛出异常
return ((Comparable) lhs).compareTo(rhs);
}
}

public void makeEmpty() {
this.root = null;
}

public boolean isEmpty() {
return this.root == null;
}

public boolean contains(AnyType x) {
return contains(x, root);
}

public AnyType findMax() {
if (isEmpty()) {
throw new BufferUnderflowException();
} else {
return findMax(root).element;
}
}

public AnyType findMin() {
if (isEmpty()) {
throw new BufferUnderflowException();
} else {
return findMin(root).element;
}
}

public void insert(AnyType x) {
root = insert(x, root);
}

public void remove(AnyType x) {
root = remove(x, root);
}

public void printTree() {
// todo
}

/**
* 递归判断树中是否包含项为x节点
*
* @param x 需要查找的数据x
* @param t 二叉查找树的根节点
* @return 如果树存在元素x返回true,否则返回false
*/
public boolean contains(AnyType x, BinaryNode<AnyType> t) {
// 如果节点为null,终止递归,该递归是尾递归(可以使用尾递归的原因是该递归很明显栈深为O(logN))
if (t == null) {
return false;
}
// 查找的元素与根节点比较
int compareResult = myCompare(x, t.element);
// 如果x比根节点小,则继续找左子树
if (compareResult < 0) {
return contains(x, t.left);
} else if (compareResult > 0) {
// 如果x比根节点大,则继续找右子树
return contains(x, t.right);
} else {
// x等于根节点,返回true,跳出递归
return true;
}
}

// 递归查询二叉查找树的最小的元素
private BinaryNode<AnyType> findMin(BinaryNode<AnyType> t) {
if (t == null) {
return null;
} else if (t.left == null) {
// 尾递归的出口,最深的左子节点即为最小元素,尾递归可以改为如下while循环
return t;
}
return findMin(t.left);
}

// 非递归查询二叉查找树的最大的元素
private BinaryNode<AnyType> findMax(BinaryNode<AnyType> t) {
if (t != null) {
while (t.right != null) {
t = t.right;
}
}
return t;
}

/**
* 将元素插入二叉查找树 (注意,该二叉查找树不是平衡的,该例都是插入到叶子结点后(不需要平衡)),建议手画理解
*
* @param x 要插入的元素x
* @param t 该二叉查找树的根节点,用来确定是哪一棵树
* @return t:插入的二叉树的根节点
*/
private BinaryNode<AnyType> insert(AnyType x, BinaryNode<AnyType> t) {
// 递归出栈条件:如果递归结果的根节点为null,则说明可以此处插入
if (t == null) {
return new BinaryNode<>(x, null, null);
}
int compareResult = myCompare(x, t.element);
if (compareResult < 0) {
// 此处说明递归:如果x比root小,则root.left 作为新的root进行递归调用,记作rootL,进入递归后,如果为null
// 则说明此处为插入点,返回一个new BinaryNode(出栈条件),跳出底层递归,此时t.left进行接收,从而实现插入
// 注意此处的左侧的t.left的t,是由insert递归传入的,可以是上面说的rootL,t.left实际上确定了。t的left指针指向哪一个节点
t.left = insert(x, t.left);
} else if (compareResult > 0) {
t.right = insert(x, t.right);
} else {
// 如果要插入的元素与二叉查找树的某个元素相同(可以将相同的元素保留在另一个结果当中)

}
// 此处的情况是,当t != null才能走到这里,每一层递归,返回的是root,用于上述的t.left或t.right
// 递归到最后返回到第一次的时候,则为初始传入的t的根节点
return t;
}

/**
* 从二叉查找树中删除元素x(注意:该二叉查找树不是平衡的,对于删除的节点只有一个,则直接移动这唯一的一个节点上去)
* 对于有两个子节点的,该例x节点删除后,移动上去的都是x节点的右子树的最小的节点(该节点不可能有左子树)),然后递归的删除移动上去的节点
*
* @param x 要删除的节点x
* @param t 二叉查找树的根节点
* @return t 二叉查找树的根节点
*/
private BinaryNode<AnyType> remove(AnyType x, BinaryNode<AnyType> t) {
if (t == null) {
return t;
}
int compareResult = myCompare(x, t.element);
if (compareResult < 0) {
t.left = remove(x, t.left);
} else if (compareResult > 0) {
t.right = remove(x, t.right);
} else if (t.left != null && t.right != null) {
// 找到了要删除的元素,要删除的元素有两个子节点
// 找到该节点作为root的子树的最小的节点的值,赋给该节点(用最小的元素替换删除的节点)
t.element = findMin(t.right).element;
// 该节点的右子树也要删除刚刚最小的元素(刚刚替换上去的元素)
t.right = remove(t.element, t.right);
} else {
// 只有一个子节点或没有子节点的情况,直接将子节点移动到删除的节点上去(两个子节点为null则为null)
t = t.left != null ? t.left : t.right;
}
// 递归最终会返回传入的根节点
return t;
}

private static class BinaryNode<AnyType> {

public AnyType element;
public BinaryNode<AnyType> left;
public BinaryNode<AnyType> right;

/**
* 构造一个节点,同时知名左子节点与右子节点
*
* @param element 当前节点存储的元素
* @param left 左子节点
* @param right 右子节点
*/
public BinaryNode(AnyType element, BinaryNode<AnyType> left, BinaryNode<AnyType> right) {
this.element = element;
this.left = left;
this.right = right;
}

/**
* 构造一个只有节点,无左子节点的,无右子节点的数据
*
* @param element 当前节点存储的数据
*/
public BinaryNode(AnyType element) {
this.element = element;
this.left = null;
this.right = null;
}
}
}

4.3.0 节点元素的比较实现

  二叉查找树是有序的,因此必须使存储的元素是可以比较的,要使一个类是可以比的,有两种实现方式,1种是实现Comparable接口,另一种是令其传入Comparator比较器。这里选择了第二种,通过构造方法传入比较器。如果没有传入比较器,则内部将类强转为Comparable的,如果是不可比的,这时会抛出异常。(因为查找树是必须有序的,所以不可比也应该抛出异常)。

4.3.1 contains方法

  该方法返回是否包含某个x元素,返回true或false,如果树t是空集,则返回false,如果x比t小,则继续查找左子树,否则继续查找右子树,直到查找到一个节点为null的点,说明没有。这使用递归实现。完整实例77-101行。

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
   /**
* 递归判断树中是否包含项为x节点
*
* @param x 需要查找的数据x
* @param t 二叉查找树的根节点
* @return 如果树存在元素x返回true,否则返回false
*/
public boolean contains(AnyType x, BinaryNode<AnyType> t) {
// 如果节点为null,终止递归,该递归是尾递归(可以使用尾递归的原因是该递归很明显栈深为O(logN))
if (t == null) {
return false;
}
// 查找的元素与根节点比较
int compareResult = myCompare(x, t.element);
// 如果x比根节点小,则继续找左子树
if (compareResult < 0) {
return contains(x, t.left);
} else if (compareResult > 0) {
// 如果x比根节点大,则继续找右子树
return contains(x, t.right);
} else {
// x等于根节点,返回true,跳出递归
return true;
}
}

4.3.2 findMin与findMax方法

  此两个方法,分别返回树种的最小的节点与最大的节点。对于最小节点,从root节点开始,向左子树查找,只要有左儿子就向左进行,如果左儿子节点为null,说明该节点最小元素。对于最大节点,从root节点开始,向右子树查找,只要有右儿子就向右进行,其余同查找最小节点。完整实例103-122行。
  findMIn的实现使用的尾递归,即在尾部只实现对自身的递归调用,而且无其他处理,尾递归一把会被编译器优化,但是建议写成findMax的循环。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 递归查询二叉查找树的最小的元素
private BinaryNode<AnyType> findMin(BinaryNode<AnyType> t) {
if (t == null) {
return null;
} else if (t.left == null) {
// 尾递归的出口,最深的左子节点即为最小元素,尾递归可以改为如下while循环
return t;
}
return findMin(t.left);
}

// 非递归查询二叉查找树的最大的元素
private BinaryNode<AnyType> findMax(BinaryNode<AnyType> t) {
if (t != null) {
while (t.right != null) {
t = t.right;
}
}
return t;
}

4.3.3 insert方法

  该方法实现了在树中插入一个元素,然后返回该树的root节点(通过该节点可以获取到整棵树)。由于该树不是平衡的二叉查找树,该方法使用了简单的将将元素插入到叶子节点末端。如果插入的节点比root节点小,则继续与左子树的root节点比较,重复该步骤,直到某个叶子节点,与该叶子节点比较,如果小于,获取该叶子节点的左子节点,如果节点为null,则在null的地方插入;如果大于,获取该叶子节点的右子节点,如果节点为null,则在null的地方插入。完整实例124-151。
  该方法是一个递归实现,注意递归较难理解,建议手动推导递归理解,理解每一层递归出栈的数据,后续怎么接收。以及最终结果返回的t的数据的具体含义。

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
   /**
* 将元素插入二叉查找树 (注意,该二叉查找树不是平衡的,该例都是插入到叶子结点后(不需要平衡)),建议手画理解
*
* @param x 要插入的元素x
* @param t 该二叉查找树的根节点,用来确定是哪一棵树
* @return t:插入的二叉树的根节点
*/
private BinaryNode<AnyType> insert(AnyType x, BinaryNode<AnyType> t) {
// 递归出栈条件:如果递归结果的根节点为null,则说明可以此处插入
if (t == null) {
return new BinaryNode<>(x, null, null);
}
int compareResult = myCompare(x, t.element);
if (compareResult < 0) {
// 此处说明递归:如果x比root小,则root.left 作为新的root进行递归调用,记作rootL,进入递归后,如果为null
// 则说明此处为插入点,返回一个new BinaryNode(出栈条件),跳出底层递归,此时t.left进行接收,从而实现插入
// 注意此处的左侧的t.left的t,是由insert递归传入的,可以是上面说的rootL,t.left实际上确定了。t的left指针指向哪一个节点
t.left = insert(x, t.left);
} else if (compareResult > 0) {
t.right = insert(x, t.right);
} else {
// 如果要插入的元素与二叉查找树的某个元素相同(可以将相同的元素保留在另一个结果当中,或者什么也不做)

}
// 此处的情况是,当t != null才能走到这里,每一层递归,返回的是root,用于上述的t.left或t.right
// 递归到最后返回到第一次的时候,则为初始传入的t的根节点
return t;
}

4.3.4 remove方法

  如果要删除的节点是叶子节点x,则直接删除,如果x只有一个子节点,则直接将x的子节点移动到删除的节点x即可。如果x有两个子节点,则将右子树的最小节点移动上去,然后对于右子树,继续将移动上去的元素删除,直到节点为null,则代表子树要删除的元素为null,此时跳出递归。

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
   /**
* 从二叉查找树中删除元素x(注意:该二叉查找树不是平衡的,对于删除的节点只有一个,则直接移动这唯一的一个节点上去)
* 对于有两个子节点的,该例x节点删除后,移动上去的都是x节点的右子树的最小的节点(该节点不可能有左子树)),然后递归的删除移动上去的节点
*
* @param x 要删除的节点x
* @param t 二叉查找树的根节点
* @return t 二叉查找树的根节点
*/
private BinaryNode<AnyType> remove(AnyType x, BinaryNode<AnyType> t) {
if (t == null) {
return t;
}
int compareResult = myCompare(x, t.element);
if (compareResult < 0) {
t.left = remove(x, t.left);
} else if (compareResult > 0) {
t.right = remove(x, t.right);
} else if (t.left != null && t.right != null) {
// 找到了要删除的元素,要删除的元素有两个子节点
// 找到该节点作为root的子树的最小的节点的值,赋给该节点(用最小的元素替换删除的节点)
t.element = findMin(t.right).element;
// 该节点的右子树也要删除刚刚最小的元素(刚刚替换上去的元素)
t.right = remove(t.element, t.right);
} else {
// 只有一个子节点或没有子节点的情况,直接将子节点移动到删除的节点上去(两个子节点为null则为null)
t = t.left != null ? t.left : t.right;
}
// 递归最终会返回传入的根节点
return t;
}

remove一个子节点与两个子节点的情况

  • 删除的节点4只有一个子节点:直接将子节点3移动到删除的节点即可。(将节点2的指针指向子节点)
  • 删除的节点2两个子节点:将2的值改为右子树的最小的元素3,(注意,此时的原本的3的元素节点并没有删除),然后将右子树中的3的节点按照此逻辑递归删除。
  • 如果删除的次数不多,则使用惰性删除,当元素被删除时,只是被标记删除,仍然留在树中。

4.3.5 平均查找深度

树的节点平均深度为O(logN)。一棵树的所有节点的深度的和为内部路径长
(如上图4-23:(6->2) + ((6->2) + (2->1)) + ((6->2) + (2->4)) + ((6->2) + (2->4) + (4->3)) + (6->8) = 9路径长
深度计算:1 + (1 + 1) + (1+1) + (1+1+1) + 1 = 9路径长

令D(N)是具有N个节点的树T的内部路径长,则D(1) = 0。一棵N节点的树由一棵i节点的左子树和一棵(N-i-1)节点的右子树组成。则D(N) = D(i) + D(N-i-1),但是因为在原树中,左右子树的所有节点深度会被加一,因此需要加 N - 1。则递推公式:

D(N)=D(i)+D(Ni1)+N1D(N) = D(i) + D(N-i-1) + N - 1

对于二叉查找树,所有子树大小都等可能出现,则有D(i)与D(N-i-1)的平均值是(1N)j=0N1D(j)(\frac{1}{N}) \sum_{j=0}^{N-1}D(j)。于是有

D(N)=2N[j=0N1D(j)]+N1D(N) = \frac{2}{N}[\sum_{j=0}^{N-1}D(j)] + N - 1

该值的解,可以的到平均值D(N) = O(NlogN)

4.4 AVL树

AVL是一棵自平衡的二叉查找树。
平衡:每个节点的左子树与右子树高度最大差为1。即 H最高H最低<=1|H_{最高}-H_{最低}| <= 1,对于空树,高度定义为0

 可以粗略证明,一棵AVL树的高度最多为1.44log(N+2)1.3281.44log(N+2) - 1.328。但实际高度只略大于log(N)。对于最少节点一棵高度为9的树,其左子树高度为7,右子树高度为8。在高度h的树中。最少节点S(h)=S(h1)+S(h2)+1S(h) = S(h-1) + S(h-2) + 1。对于h=0,S(h) = 1;h=1,S(h) = 2,函数S(h)与斐波那契数列相关。因此推导出上面的高度。
图4-29 二叉查找树与AVL树

如上图,左图的左子树的高度为3(3->4->2,叶子节点3高1,4高2,2高3),右子树的高度为2(7->8),差为1。右图的左子树高度为3,右子树的高度为1,相差为2,不是AVL树(叶子节点的高度为0,注意与深度的区别)。‘可以简单的看作是两棵子树的最大深度相差为大于1’。
  除去插入操作外(删除操作是惰性删除,不删除,只标记删除),所有的对树的操作时间复杂度皆为O(logN),因为插入操作会破坏树的平衡,这时需要进行旋转操作来维持树的平衡。
  当一个节点插入后,任意两棵子树的高度差为2,只有插入点到根节点的平衡会被打破,因为只有这些节点的子树发生了变化。把必须平衡的节点记为a,失去平衡的情况可能会有以下四种:

  1. 对a的左儿子的的左子树进行一次插入,失去平衡,左左(LL)。
  2. 对a的左儿子的的右子树进行一次插入,失去平衡,左右(LR)。
  3. 对a的右儿子的的左子树进行一次插入,失去平衡,左左(RL)。
  4. 对a的右儿子的的右子树进行一次插入,失去平衡,左左(RR)。

  情况1是情况4的镜像对称,该情况可以通过单次旋转完成。情况2是情况3的镜像对称,该操作发生在‘内部’的情形,需要通过双旋转平衡。

4.4.1 单旋转

4.4.1.1 左左情况(右旋)

图4-31 调整情况1的单旋转

  如上图,对于单旋转的情况1,节点k2k_2不满足AVL平衡的性质。为使树平衡,我们需要把节点X向上移动一层,节点Z向下移动一层。将树想象为灵活有重量的,现在拿住K1K_1节点(),K1K_1成为根,因为K2>K1K_2 > K_1,所以K2K_2变成K1K_1的右孩子。子树Y中的元素都大于K1K_1,小于K2K_2,所以可以将其放置到K2K_2的左孩纸的位置。
如下图是一种实际情况的处理:左左

图4-32 左左单旋转实例

  该平衡二叉树插入6后,对8而言,左子树高度为2,右子树高度为0(不存在),相差为2,只对6、7、8而言,拿住7,进行一次右旋。(树原本是平衡的,被破坏平衡后,先处理较小子树的不平衡情况,所以这里只处理8子树的情况)。

4.4.1.12右右情况(左旋)

  情况4是情况1的一种镜像对称(优先处理子树的不平衡,这里K2K_2的子树是平衡的,是对K1K_1不平衡,对K1K_1是右右情况)。实例略。

图4-33 调整情况4的单旋转

4.4.2 双旋转

4.4.2.1 左右单旋转

左右的情况:

左右双旋转

  如上图,子树Y比子树Z高2层,将其抽象成如右图所示地3个连接点与4棵子树,B、C只比D深1121\frac 12 ,把K2K_2重新作为根,K1K_1K2K_2小,因此作为子节点,同样K3K_3作为右节点。
  PS:对于左右的情况,可以先左旋,再右旋(操作次数会增加),如下图(这里假设插入到B):
下图有误,是在K2K_2的节点下插入,应该只有B或者C,且插入的高度为1,但是对旋转逻辑无影响。

调整情况2的左-右

同样的对于右左的情况:
右-左双旋转修复情况

右-左 双旋转实例
右-左双旋转实例

Avl树主代码实现(点击展开)
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
public class AvlTree<AnyType extends Comparable<AnyType>> {

// 允许的最大高度差(超过该该高度,树会失去平衡)
private static final int ALLOWED_IMBALANCE = 1;

// 根节点
private AvlNode<AnyType> root;

public AvlNode<AnyType> getRoot() {
return root;
}

public AvlTree() {
this.root = null;
}

public void insert(AnyType x) {
root = insert(x, root);
}

public void remove(AnyType x) {
root = remove(x, root);
}

/**
* 计算节点的高度,空树的高度定义为-1,叶子节点的高度定义为0
* @param t 节点
* @return 节点的高度
*/
private int height(AvlNode<AnyType> t) {
return t == null ? -1 : t.height;
}
// 供外部调用获取高度
public int height() {
return height(root);
}

/**
* 插入节点x
* @param x 要插入的值x
* @param t 插入的tree,这里一般是root节点
* @return
*/
private AvlNode<AnyType> insert(AnyType x, AvlNode<AnyType> t) {
if (t == null) {
return new AvlNode<>(x, null, null);
}
int compareResult = x.compareTo(t.element);
if (compareResult < 0) {
//
t.left = insert(x, t.left);
} else if (compareResult > 0) {
t.right = insert(x, t.right);
} else {
// 添加的节点已经存在
}
return balance(t);
}

/**
* 平衡Avl树
* @param t 树的根节点t
* @return 平衡后的跟节点t
*/
private AvlNode<AnyType> balance(AvlNode<AnyType> t) {
if (t == null) {
return t;
}
// 如果左子树的高度大于右子树超过1,则说明左边失去平和,需要右旋
if (height(t.left) - height(t.right) > ALLOWED_IMBALANCE) {
// 如果左子树的左子树的高度 大于等于 左子树右子树的高度(左左情况,单右旋即可)
if (height(t.left.left) >= height(t.left.right)) {
t = rotateWithLeftChild(t);
} else {
// 否则,为左右情况(root的左子树 - 右子树> 1, 并且root.left.left < root.left.right)。
// 需要(左)右双旋转
t = doubleWithLeftChild(t);
}
} else {
// 与上面相反
if (height(t.right) - height(t.left) > ALLOWED_IMBALANCE) {
if (height(t.right.right) >= height(t.right.left)) {
t = rotateWithRightChild(t);
} else {
t = doubleWithRightChild(t);
}
}
}
t.height = Math.max(height(t.left), height(t.right)) + 1;
return t;
}

/**
* 向右单旋转 (左开弓(<) 变为右开弓形(>))
* @param k2 树根节点
* @return 平衡后的根节点
*/
private AvlNode<AnyType> rotateWithLeftChild(AvlNode<AnyType> k2) {
// 记录k2的左节点 k1
AvlNode<AnyType> k1 = k2.left;
// 将k2的左节点设置为k1的右节点(原本k2的左节点的右节点)
k2.left = k1.right;
// k2变为k1的右节点
k1.right = k2;
// k2的高度与k1的高度变化,需要重新计算
k2.height = Math.max(height(k2.left), height(k2.right)) + 1;
k1.height = Math.max(height(k1.left), k2.height) + 1;
return k1;
}

/**
* 向(左)右双旋转(先左旋root的左子树,再右旋root)
* @param root 树根节点
* @return 平衡后的根节点
*/
private AvlNode<AnyType> doubleWithLeftChild(AvlNode<AnyType> root) {
root.left = rotateWithRightChild(root.left);
return rotateWithLeftChild(root);
}

/**
* 向左单旋(与向左旋转相反)
* @param root 根节点
* @return 平衡后的根节点
*/
private AvlNode<AnyType> rotateWithRightChild(AvlNode<AnyType> root) {
AvlNode<AnyType> k1 = root.right;
root.right = k1.left;
k1.left = root;
root.height = Math.max(height(root.left), height(root.right)) + 1;
k1.height = Math.max(height(k1.left), height(k1.right)) + 1;
return k1;
}

/**
* 从Avl树中删除节点t
* @param x 要删除的节点x
* @param t 根节点t
* @return 删除节点后x的根节点(删除后需要重新平衡)
*/
private AvlNode<AnyType> remove(AnyType x, AvlNode<AnyType> t) {
if (t == null) {
return null;
}
int compareResult = x.compareTo(t.element);
if (compareResult < 0) {
t.left = remove(x, t.left);
} else if (compareResult > 0) {
t.right = remove(x, t.right);
} else if (t.left != null && t.right != null) {
// x=t 并且t的左右子节点不为空
// 将root节点的值设置为右子树最小值,然后删除右子树的最小值
t.element = findMin(t.right).element;
t.right = remove(t.element, t.right);
} else {
// x=t 并且t的左右子节点 至少右一个为空
t = t.left != null ? t.left : t.right;
}
return balance(t);
}

/**
* 在root节点的所有子树中查找最小的节点
* @param root
* @return 找到的最小的节点
*/
public AvlNode<AnyType> findMin(AvlNode<AnyType> root) {
if (root != null) {
while (root.left != null) {
root = root.left;
}
}
return root;
}

/**
* 向(右)左双旋转(先右旋root的右子树,再左旋root)
* @param root 树根节点
* @return 平衡后的根节点
*/
private AvlNode<AnyType> doubleWithRightChild(AvlNode<AnyType> root) {
root.right = rotateWithLeftChild(root.right);
return rotateWithRightChild(root);
}

private static class AvlNode<AnyType> {
// 节点存储的值
public AnyType element;
// 左子节点
public AvlNode<AnyType> left;
// 右子节点
public AvlNode<AnyType> right;
// AVL树的高度
public int height;

public AvlNode(AnyType theElement) {
this.element = theElement;
this.left = null;
this.right = null;
}

public AvlNode(AnyType theElement, AvlNode<AnyType> lt, AvlNode<AnyType> rt) {
this.element = theElement;
this.left = lt;
this.right = rt;
// 叶子节点的高度为0,空树的高度为-1
this.height = 0;
}
}
}

打印树中的元素,测试用(点击展开)
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
// 前序遍历(根,左右),测试用
public void preOrderPrint(AvlNode<AnyType> node){
if(node != null) {
System.out.print(node.element + "->");
preOrderPrint(node.left);
preOrderPrint(node.right);
}
}

// 中序遍历(左根右),测试用
public void inOrderPrint(AvlNode<AnyType> node){
if(node != null) {
inOrderPrint(node.left);
System.out.println(root.element + "->");
inOrderPrint(root.right);
}
}

// 后序遍历(左右根),测试用
public void postOrderPrint(AvlNode<AnyType> node){
if(node != null) {
postOrderPrint(node.left);
postOrderPrint(root.right);
System.out.println(root.element + "->");
}
}