一、AVL 树

  在计算机科学中,AVL树是最早被发明的自平衡二叉查找树。在AVL树中,任一节点对应的两棵子树的最大高度差为 1,因此它也被称为高度平衡树。查找、插入和删除在平均和最坏情况下的时间复杂度都是 O(log(n))。插入和删除元素的操作则可能需要借由一次或多次树旋转,以实现树的重新平衡。

  节点的平衡因子是它的左子树的高度减去它的右子树的高度(有时相反)。带有平衡因子 1、0 或 -1 的节点被认为是平衡的。带有平衡因子 -2 或 2 的节点被认为是不平衡的,并需要重新平衡这个树。平衡因子可以直接存储在每个节点中,或从可能存储在节点中的子树高度计算出来。

  大多数 BST 操作(例如,搜索,最大,最小,插入,删除等)花费 O(h) 时间,其中 h 是 BST 的高度。对于偏斜的二叉树,这些操作的成本可能变为 O(n)。如果确保每次插入和删除后树的高度都保持 O(log2n),则可以保证所有这些操作的 O(log2n)上限。AVL树的高度始终为 O(log2n),其中 n 是树中的节点数。

二、AVL 树的旋转

  AVL 树在普通的插入和删除节点时都会使得树失去平衡,这时我们需要一些操作来把树恢复平衡,这些操作叫做AVL树的旋转,分为左旋右旋

  T1,T2 和 T3 是树 y(左边) 或 x(右边) 的子树:

     y                                 x
/ \   Right Rotation / \
x T3   - - - - - - - > T1 y
/ \   < - - - - - - - / \
T1 T2   Left Rotation T2 T3

  以上两个树中的键都遵循以下顺序(二叉查找树的性质):

  keys(T1) < key(x) < keys(T2) < key(y) < keys(T3)。

 1     /**
2 * 右旋转以y为根的子树
3 *
4 * @param y
5 * @return
6 */
7 private Node rightRoate(Node y) {
8 Node x = y.left;
9 Node T2 = x.right;
10
11 /* 执行旋转 */
12 x.right = y;
13 y.left = T2;
14
15 /* 更新高度 */
16 y.height = max(height(y.left), height(y.right)) + 1;
17 x.height = max(height(x.left), height(x.right)) + 1;
18
19 return x;
20 }
21
22 /**
23 * 左旋转以x为根的子树
24 *
25 * @param x
26 * @return
27 */
28 private Node leftRoate(Node x) {
29 Node y = x.right;
30 Node T2 = y.left;
31
32 /* 执行旋转 */
33 y.left = x;
34 x.right = T2;
35
36 /* 更新高度 */
37 x.height = max(height(x.left), height(x.right)) + 1;
38 y.height = max(height(y.left), height(y.right)) + 1;
39
40 return y;
41 }

三、AVL 树的插入操作

插入要遵循的步骤:

  新插入的节点为 w

  1)对 w 执行标准 BST 插入。

  2)从 w 开始,向上移动并找到第一个不平衡节点。令 z 为第一个不平衡节点,y 为从 w 到 z 的路径中 z 的子代,x 为从 w 到 z 的路径中 z 的孙代。

  3)通过对以 z 为根的子树执行适当的旋转重新平衡树。可能有 4 种可能的情况需要处理,因为 x,y 和 z 可以 4 种方式排列。以下是可能的 4 种排列方式:

    a)y 是 z 的左子代,x 是 y 的左子代(左案例)

         z                                      y
/ \ / \
y T4 Right Rotate (z) x z
/ \ - - - - - - - - -> / \ / \
x T3 T1 T2 T3 T4
/ \
T1 T2

    b)y 是 z 的左子代,x 是 y 的右子代(左案例)

     z                               z                           x
/ \ / \ / \
y T4 Left Rotate (y) x T4 Right Rotate(z) y z
/ \ - - - - - - - - -> / \ - - - - - - - -> / \ / \
T1 x y T3 T1 T2 T3 T4
/ \ / \
T2 T3 T1 T2

    c)y 是 z 的右子代,x 是 y 的右子代(右右案例)

  z                                y
/ \ / \
T1 y Left Rotate(z) z x
/ \ - - - - - - - -> / \ / \
T2 x T1 T2 T3 T4
/ \
T3 T4

    d)y 是 z 的右子代,x 是 y 的左子代(右左案例)

   z                            z                            x
/ \ / \ / \
T1 y Right Rotate (y) T1 x Left Rotate(z) z y
/ \ - - - - - - - - -> / \ - - - - - - - -> / \ / \
x T4 T2 y T1 T2 T3 T4
/ \ / \
T2 T3 T3 T4

  插入操作:

 1     /**
2 * AVL树的插入
3 *
4 * @param node
5 * @param key
6 * @return
7 */
8 private Node insert(Node node, int key) {
9 /* 执行正常的BST插入 */
10 if (node == null)
11 return (new Node(key));
12
13 if (key < node.key)
14 node.left = insert(node.left, key);
15 else if (key > node.key)
16 node.right = insert(node.right, key);
17 else // 不允许重复的key
18 return node;
19
20 /* 更新此祖先节点的高度 */
21 node.height = 1 + max(height(node.left), height(node.right));
22
23 /* 获取此祖先的平衡因子以检查此节点是否变为不平衡 */
24 int balance = getBalance(node);
25
26 /* 如果此节点变得不平衡,则存在有4种情况 */
27 if (balance > 1 && key < node.left.key) {
28 return rightRoate(node);
29 }
30
31 if (balance < -1 && key > node.right.key) {
32 return leftRoate(node);
33 }
34
35 if (balance > 1 && key > node.left.key) {
36 node.left = leftRoate(node.left);
37 return rightRoate(node);
38 }
39
40 if (balance < -1 && key < node.right.key) {
41 node.right = rightRoate(node.right);
42 return leftRoate(node);
43 }
44
45 return node;
46 }

四、AVL 树的删除操作

删除要遵循的步骤:

  令 w 为要删除的节点

  1)对 w 执行标准BST删除。

  2)从 w 开始,向上移动并找到第一个不平衡节点。令 z 为第一个不平衡节点,y 为 z 的较大孩子,x 为 y 的较大孩子。请注意,x 和 y 的定义与此处的插入不同。

  3)通过对以 z 为根的子树执行适当的旋转来重新平衡树。有 4 种可能的情况需要处理,因为 x,y 和 z 可以 4 种方式排列。以下是可能的 4 种排列方式:

    a)y 是 z 的左子代,x是y的左子代(左案例)

         z                                      y
/ \ / \
y T4 Right Rotate (z) x z
/ \ - - - - - - - - -> / \ / \
x T3 T1 T2 T3 T4
/ \
T1 T2

    b)y 是 z 的左子代,x 是 y 的右子代(左案例)

     z                               z                           x
/ \ / \ / \
y T4 Left Rotate (y) x T4 Right Rotate(z) y z
/ \ - - - - - - - - -> / \ - - - - - - - -> / \ / \
T1 x y T3 T1 T2 T3 T4
/ \ / \
T2 T3 T1 T2

    c)y 是 z 的右子代,x 是 y 的右子代(右右案例)

  z                                y
/ \ / \
T1 y Left Rotate(z) z x
/ \ - - - - - - - -> / \ / \
T2 x T1 T2 T3 T4
/ \
T3 T4

    d)y 是 z 的右子代,x 是 y 的左代子(右左案例)

   z                            z                            x
/ \ / \ / \
T1 y Right Rotate (y) T1 x Left Rotate(z) z y
/ \ - - - - - - - - -> / \ - - - - - - - -> / \ / \
x T4 T2 y T1 T2 T3 T4
/ \ / \
T2 T3 T3 T4

  与插入不同,在删除中,在 z 处进行旋转后,可能必须在 z 的祖先处进行旋转。因此,我们必须继续追踪路径,直到到达根为止。

  删除操作:

 1     /**
2 * AVL树的删除
3 *
4 * @param N
5 * @return
6 */
7 private Node deleteNode(Node root, int key) {
8 if (root == null)
9 return root;
10 /* 如果要删除的key小于root的key,则它位于左子树中 */
11 if (key < root.key)
12 root.left = deleteNode(root.left, key);
13
14 /* 如果要删除的key大于root的key,则它位于右子树中 */
15 else if (key > root.key)
16 root.right = deleteNode(root.right, key);
17
18 /* 如果key与root的key相同,则这个节点要被删除 */
19 else {
20 /* 只有一个孩子或没有孩子的节点 */
21 if ((root.left == null) || (root.right == null)) {
22 Node temp = null;
23 if (temp == root.left)
24 temp = root.right;
25 else
26 temp = root.left;
27
28 /* 没有孩子的情况 */
29 if (temp == null) {
30 temp = root;
31 root = null;
32 } else { // 只有一个孩子
33 root = temp; // 复制非空孩子的内容
34 }
35
36 } else {
37 /* 有两个子节点的节点:获取后继节点(在右侧子树中最小) */
38 Node temp = minValueNode(root.right);
39 /* 将后继节点的数据复制到此节点 */
40 root.key = temp.key;
41 /* 删除后继节点 */
42 root.right = deleteNode(root.right, temp.key);
43 }
44 }
45
46 /* 如果树只有一个节点,则返回 */
47 if (root == null)
48 return root;
49
50 /* 更新当前节点的高度 */
51 root.height = max(height(root.left), height(root.right)) + 1;
52 /* 获取此节点的平衡因素 */
53 int balance = getBalance(root);
54
55 /* 如果此节点变得不平衡,则有4种情况 */
56 if (balance > 1 && getBalance(root.left) >= 0) {
57 return rightRoate(root);
58 }
59
60 if (balance > 1 && getBalance(root.left) < 0) {
61 root.left = leftRoate(root.left);
62 return rightRoate(root);
63 }
64
65 if (balance < -1 && getBalance(root.right) <= 0) {
66 return leftRoate(root);
67 }
68
69 if (balance < -1 && getBalance(root.right) > 0) {
70 root.right = rightRoate(root.right);
71 return leftRoate(root);
72 }
73
74 return root;
75 }

本文源代码:

  1 package algorithm;
2
3 /**
4 * 自平衡二叉树,左右子树的高度差不大于1
5 */
6 public class AVLTree {
7
8 private Node root;
9
10 /**
11 * 树高度
12 *
13 * @param N
14 * @return
15 */
16 private int height(Node N) {
17 if (N == null) {
18 return 0;
19 }
20 return N.height;
21 }
22
23 private int max(int a, int b) {
24 return Math.max(a, b);
25 }
26
27 /**
28 * 右旋转以y为根的子树
29 *
30 * @param y
31 * @return
32 */
33 private Node rightRoate(Node y) {
34 Node x = y.left;
35 Node T2 = x.right;
36
37 /* 执行旋转 */
38 x.right = y;
39 y.left = T2;
40
41 /* 更新高度 */
42 y.height = max(height(y.left), height(y.right)) + 1;
43 x.height = max(height(x.left), height(x.right)) + 1;
44
45 return x;
46 }
47
48 /**
49 * 左旋转以x为根的子树
50 *
51 * @param x
52 * @return
53 */
54 private Node leftRoate(Node x) {
55 Node y = x.right;
56 Node T2 = y.left;
57
58 /* 执行旋转 */
59 y.left = x;
60 x.right = T2;
61
62 /* 更新高度 */
63 x.height = max(height(x.left), height(x.right)) + 1;
64 y.height = max(height(y.left), height(y.right)) + 1;
65
66 return y;
67 }
68
69 /**
70 * 获取N结点的平衡
71 *
72 * @param N
73 * @return
74 */
75 private int getBalance(Node N) {
76 if (N == null)
77 return 0;
78
79 return height(N.left) - height(N.right);
80 }
81
82 /**
83 * AVL树的插入
84 *
85 * @param node
86 * @param key
87 * @return
88 */
89 private Node insert(Node node, int key) {
90 /* 执行正常的BST插入 */
91 if (node == null)
92 return (new Node(key));
93
94 if (key < node.key)
95 node.left = insert(node.left, key);
96 else if (key > node.key)
97 node.right = insert(node.right, key);
98 else // 不允许重复的key
99 return node;
100
101 /* 更新此祖先节点的高度 */
102 node.height = 1 + max(height(node.left), height(node.right));
103
104 /* 获取此祖先的平衡因子以检查此节点是否变为不平衡 */
105 int balance = getBalance(node);
106
107 /* 如果此节点变得不平衡,则存在有4种情况 */
108 if (balance > 1 && key < node.left.key) {
109 return rightRoate(node);
110 }
111
112 if (balance < -1 && key > node.right.key) {
113 return leftRoate(node);
114 }
115
116 if (balance > 1 && key > node.left.key) {
117 node.left = leftRoate(node.left);
118 return rightRoate(node);
119 }
120
121 if (balance < -1 && key < node.right.key) {
122 node.right = rightRoate(node.right);
123 return leftRoate(node);
124 }
125
126 return node;
127 }
128
129 /**
130 * 给定一个非空的二叉查找树,返回树中最小key值的结点(注意无需遍历整个树)
131 *
132 * @param node
133 * @return
134 */
135 private Node minValueNode(Node node) {
136 Node current = node;
137
138 while (current.left != null)
139 current = current.left;
140
141 return current;
142 }
143
144 /**
145 * AVL树的删除
146 *
147 * @param N
148 * @return
149 */
150 private Node deleteNode(Node root, int key) {
151 if (root == null)
152 return root;
153 /* 如果要删除的key小于root的key,则它位于左子树中 */
154 if (key < root.key)
155 root.left = deleteNode(root.left, key);
156
157 /* 如果要删除的key大于root的key,则它位于右子树中 */
158 else if (key > root.key)
159 root.right = deleteNode(root.right, key);
160
161 /* 如果key与root的key相同,则这个节点要被删除 */
162 else {
163 /* 只有一个孩子或没有孩子的节点 */
164 if ((root.left == null) || (root.right == null)) {
165 Node temp = null;
166 if (temp == root.left)
167 temp = root.right;
168 else
169 temp = root.left;
170
171 /* 没有孩子的情况 */
172 if (temp == null) {
173 temp = root;
174 root = null;
175 } else { // 只有一个孩子
176 root = temp; // 复制非空孩子的内容
177 }
178
179 } else {
180 /* 有两个子节点的节点:获取后继节点(在右侧子树中最小) */
181 Node temp = minValueNode(root.right);
182 /* 将后继节点的数据复制到此节点 */
183 root.key = temp.key;
184 /* 删除后继节点 */
185 root.right = deleteNode(root.right, temp.key);
186 }
187 }
188
189 /* 如果树只有一个节点,则返回 */
190 if (root == null)
191 return root;
192
193 /* 更新当前节点的高度 */
194 root.height = max(height(root.left), height(root.right)) + 1;
195 /* 获取此节点的平衡因素 */
196 int balance = getBalance(root);
197
198 /* 如果此节点变得不平衡,则有4种情况 */
199 if (balance > 1 && getBalance(root.left) >= 0) {
200 return rightRoate(root);
201 }
202
203 if (balance > 1 && getBalance(root.left) < 0) {
204 root.left = leftRoate(root.left);
205 return rightRoate(root);
206 }
207
208 if (balance < -1 && getBalance(root.right) <= 0) {
209 return leftRoate(root);
210 }
211
212 if (balance < -1 && getBalance(root.right) > 0) {
213 root.right = rightRoate(root.right);
214 return leftRoate(root);
215 }
216
217 return root;
218 }
219
220 /**
221 * 先序遍历
222 *
223 * @param node
224 */
225 private void preOrder(Node node) {
226 if (node != null) {
227 System.out.print(node.key + " ");
228 preOrder(node.left);
229 preOrder(node.right);
230 }
231 }
232
233 public static void main(String[] args) {
234
235 /* ---------------------------------------------------- */
236
237 AVLTree tree = new AVLTree();
238
239 /* 构造AVL树 */
240 tree.root = tree.insert(tree.root, 9);
241 tree.root = tree.insert(tree.root, 5);
242 tree.root = tree.insert(tree.root, 10);
243 tree.root = tree.insert(tree.root, 0);
244 tree.root = tree.insert(tree.root, 6);
245 tree.root = tree.insert(tree.root, 11);
246 tree.root = tree.insert(tree.root, -1);
247 tree.root = tree.insert(tree.root, 1);
248 tree.root = tree.insert(tree.root, 2);
249
250 /* 构造的AVL树:
251 9
252 / \
253 1 10
254 / \ \
255 0 5 11
256 / / \
257 -1 2 6
258 */
259 System.out.println("Preorder traversal of constructed tree is : ");
260 tree.preOrder(tree.root);
261
262 tree.root = tree.deleteNode(tree.root, 10);
263
264 /* 删除10后的AVL树:
265 1
266 / \
267 0 9
268 / / \
269 -1 5 11
270 / \
271 2 6
272 */
273 System.out.println();
274 System.out.println("Preorder traversal after "+
275 "deletion of 10 :");
276 tree.preOrder(tree.root);
277 }
278 }
279
280 class Node {
281 int key, height;
282 Node left, right;
283
284 Node(int d) {
285 key = d;
286 height = 1;
287 }
288
289 }

AVL树的插入和删除的更多相关文章

  1. AVL 树的插入、删除、旋转归纳

    参考链接: http://blog.csdn.net/gabriel1026/article/details/6311339   1126号注:先前有一个概念搞混了: 节点的深度 Depth 是指从根 ...

  2. AVL树的插入与删除

    AVL 树要在插入和删除结点后保持平衡,旋转操作必不可少.关键是理解什么时候应该左旋.右旋和双旋.在Youtube上看到一位老师的视频对这个概念讲解得非常清楚,再结合算法书和网络的博文,记录如下. 1 ...

  3. 创建AVL树,插入,删除,输出Kth Min

    https://github.com/TouwaErioH/subjects/tree/master/C%2B%2B/PA2 没有考虑重复键,可以在结构体内加一个int times. 没有考虑删除不存 ...

  4. AVL树的插入操作(旋转)图解

    =================================================================== AVL树的概念       在说AVL树的概念之前,我们需要清楚 ...

  5. B树和B+树的插入、删除图文详解

    简介:本文主要介绍了B树和B+树的插入.删除操作.写这篇博客的目的是发现没有相关博客以举例的方式详细介绍B+树的相关操作,由于自身对某些细节也感到很迷惑,通过查阅相关资料,对B+树的操作有所顿悟,写下 ...

  6. B树和B+树的插入、删除图文详解(good)

    B树和B+树的插入.删除图文详解 1. B树 1. B树的定义 B树也称B-树,它是一颗多路平衡查找树.我们描述一颗B树时需要指定它的阶数,阶数表示了一个结点最多有多少个孩子结点,一般用字母m表示阶数 ...

  7. AVL树的插入删除查找算法实现和分析-1

    至于什么是AVL树和AVL树的一些概念问题在这里就不多说了,下面是我写的代码,里面的注释非常详细地说明了实现的思想和方法. 因为在操作时真正需要的是子树高度的差,所以这里采用-1,0,1来表示左子树和 ...

  8. B+树的插入、删除(附源代码)

    B+ Tree Index B+树的插入 B+树的删除 完整测试代码 Basic B+树和B树类似(有关B树:http://www.cnblogs.com/YuNanlong/p/6354029.ht ...

  9. MySQL B+树 的插入与删除

    一.MySQL Index 的插入 有如下B+树,其高度为2,每页可存放4条记录,扇出为5.所有记录都在叶子节点上, 并且是顺序存放,如果用户从最左边的叶子节点开始顺序遍历,可以得到所有简直的顺序 排 ...

随机推荐

  1. 远程桌面连接(mstsc)全攻略

    打算从今天开始,写一写我经常用的,有长时间使用经验的东西,与大家分享,就从mstsc开始吧! mstsc应该是在Windows中,除了calc.cmd.notepad.mspaint,我使用率最高的系 ...

  2. Ubuntu管理员手册

    hostname cat /etc/hosts apt-get install iproute2 iproute2-doc ip a ps -aux

  3. MySql分表、分库、分片和分区的区别

    一.前言 数据库的数据量达到一定程度之后,为避免带来系统性能上的瓶颈.需要进行数据的处理,采用的手段是分区.分片.分库.分表. 二.分片(类似分库) 分片是把数据库横向扩展(Scale Out)到多个 ...

  4. 【PHP数据结构】队列的相关逻辑操作

    在逻辑结构中,我们已经学习了一个非常经典的结构类型:栈.今天,我们就来学习另外一个也是非常经典的逻辑结构类型:队列.相信不少同学已经使用过 redis . rabbitmq 之类的缓存队列工具.其实, ...

  5. PHP大文件读取操作

    简单的文件读取,一般我们会使用 file_get_contents() 这类方式来直接获取文件的内容.不过这种函数有个严重的问题是它会把文件一次性地加载到内存中,也就是说,它会受到内存的限制.因此,加 ...

  6. UML类图及其JAVA的代码实现

    推荐 : https://my.oschina.net/u/3635618/blog/3165129 http://www.uml.org.cn/oobject/201104212.asp

  7. Shell系列(15)- 数值运算方法

    数值运算-方法1 declare -i [root@localhost ~]# aa=11 [root@localhost ~]# aa=22   #给变量aa和bb赋值 [root@localhos ...

  8. vue 改变路由参数

    更改路由传递的参数: const query = JSON.parse(JSON.stringify(this.$route.query)) // 获取路由参数信息 query.id = Name / ...

  9. Redis多种数据类型以及使用场景

    SDS简单动态字符串 struct sdshdr { // 记录buf数组中已使用字节的数量 // 等于SDS所保存字符串的长度 int len; // 记录buf数组中未使用字节的数量 int fr ...

  10. CSS 小技巧 | 一行代码实现头像与国旗的融合

    到国庆了,大家都急着给祖国母亲庆生. 每年每到此时,微信朋友圈就会流行起给头像装饰上国旗,而今年又流行这款: emm,很不错. 那么,将一张国旗图片与我们的头像,快速得到想要的头像,使用 CSS 如何 ...