一、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. [Linux系列]DNS系列理论笔记与DNS服务器配置

    0x01 基础术语 DNS(Domain Name System,域名系统),域名和IP地址相互映射的一个分布式数据库,简而言之就是通过更易记忆的域名代替IP去访问一个网站. FQDN(Fully Q ...

  2. Map集和

    目录 Map 特点 继承树 常用方法 entrySet 方法 HashMap 特点 HashMap 的重要常量 存储结构 jdk1.8 总结 面试题 HashMap存储自定义类型键值 LinkedHa ...

  3. visual studio下载速度为0解决方法

    步骤: 一,更改网络设置 二,cmd刷新dns 一,更改网络设置 1,点开控制面板,打开网络和Internet 2,点击网络和共享中心 3,点击你连接的网络,那个是你连接的WIFI名字 4,点击属性 ...

  4. 剑指 Offer 60. n个骰子的点数

    剑指 Offer 60. n个骰子的点数 把n个骰子扔在地上,所有骰子朝上一面的点数之和为s.输入n,打印出s的所有可能的值出现的概率. 你需要用一个浮点数数组返回答案,其中第 i 个元素代表这 n ...

  5. BF算法(串模式匹配算法)

    主串和子串 主串与子串:如果串 A(如 "shujujiegou")中包含有串 B(如 "ju"),则称串 A 为主串,串 B 为子串.主串与子串之间的关系可简 ...

  6. python+echarts+flask实现对全国疫情数据的爬取并可视化展示

    用Python进行数据爬取并存储到数据库,3.15学习总结(Python爬取网站数据并存入数据库) - 天岁 - 博客园 (cnblogs.com) 通过echarts+flask实现数据的可视化展示 ...

  7. python 小鸡飞行小游戏

    python 小鸡飞行小游戏 用空格键控制小鸡飞行 代码 import pygame.freetype import sys import random pygame.init() screen = ...

  8. 使用Visual Studio Code 开发 ESP8266

    使用Visual Studio Code 开发 ESP8266 ESP8266+ArduinoIDE+VSCode开发ESP8266. 首先说明一下ESP8266并不是某一WiFi模块的名字(我以前是 ...

  9. Java面向对象系列(9)- 方法重写

    为什么需要重写? 父类的功能,子类不一定需要,或者不一定满足 场景一 重写都是方法的重写,和属性无关 父类的引用指向了子类 用B类新建了A类的对象,把A赋值给了B,这时候B是A,A又继承了B类,向上转 ...

  10. Shell系列(18)- 什么是正则表达式

    概念: 正则表达式是用于描述字符排列和匹配模式的一种语法 它主要用于字符串的模式分割.匹配.查找及替换操作.