对比二叉树

二叉树的搜索时间复杂度是log(n),但是在某些条件由于二叉树的不平衡很容易退化成链表,导致时间复杂度从O(log(n))变成O(n)。

AVL树则在二叉树的基础上添加了平衡功能,确保不会在插入和删除中导致节点失去平衡,时间复杂度稳定保持在O(log(n))。

添加节点失去平衡

LL 在A的左子树根节点的左子树上插入节点而破坏平衡 右旋转
RR 在A的右子树根节点的右子树上插入节点而破坏平衡 左旋转
LR 在A的右子树根节点的右子树上插入节点而破坏平衡 先左旋后右旋
RL 在A的右子树根节点的左子树上插入节点而破坏平衡 先右旋后左旋
  • 左子树高: 右旋
  • 右子树高: 左旋
  • 平衡因子: |右子树高度-左子树高度| <= 1

    删除节点失去平衡

    从AVL树中删除,可以通过把要删除的节点向下旋转成一个叶子节点,接着直接移除这个叶子节点来完成。因为在旋转成叶子节点期间最多有log n个节点被旋转,而每次AVL旋转耗费固定的时间,所以删除处理在整体上耗费O(log n) 时间。

    目前实现的二叉树的删除和AVL树的删除操作是一样的,都是找到删除节点,然后交换成叶子节点,删除.

  • 删除叶子节点

  • 删除只有一颗子树的节点
    • 只有左子树
    • 只有右子树
  • 删除存在2颗子树的节点

AVL可视化

WIKI

https://zh.wikipedia.org/wiki/AVL%E6%A0%91#%E5%88%A0%E9%99%A4

AVL树

  1. package io.github.chenshun00.web.tree;
  2. /**
  3. * @author chenshun00@gmail.com
  4. * @since 2021/8/14 10:26 上午
  5. */
  6. public class AVLTree {
  7. private Node root;
  8. public static void main(String[] args) {
  9. AVLTree avlTree = new AVLTree();
  10. avlTree.insert(1);
  11. avlTree.insert(2);
  12. avlTree.insert(3);
  13. avlTree.insert(4);
  14. avlTree.insert(5);
  15. avlTree.insert(6);
  16. avlTree.insert(7);
  17. avlTree.insert(8);
  18. avlTree.insert(9);
  19. avlTree.insert(999);
  20. // avlTree.traverse();
  21. // System.out.println(avlTree.isAVLTree());
  22. // final Node node = avlTree.findNode(9);
  23. // System.out.println(node);
  24. avlTree.delete(999);
  25. System.out.println("删除节点4===>" + avlTree.delete(4));
  26. // avlTree.traverse();
  27. System.out.println("删除节点3===>" + avlTree.delete(3));
  28. System.out.println("删除节点6===>" + avlTree.delete(6));
  29. System.out.println("删除节点1===>" + avlTree.delete(1));
  30. avlTree.traverse();
  31. }
  32. public boolean isAVLTree() {
  33. if (root == null) {
  34. return false;
  35. }
  36. return avl(root, true);
  37. }
  38. public Node findNode(int data) {
  39. return root == null ? null : doFindNode(data);
  40. }
  41. //删除操作
  42. public boolean delete(int data) {
  43. //找节点,找不到直接返回
  44. final Node node = findNode(data);
  45. if (node == null) {
  46. return false;
  47. }
  48. //如果节点是root
  49. if (node == root) {
  50. //如果root是叶子
  51. if (isLeaf(node)) {
  52. root = null;
  53. } else {
  54. //如果root还有子树
  55. replace(node);
  56. }
  57. return true;
  58. }
  59. //被移除的是叶子节点
  60. if (isLeaf(node)) {
  61. //找到叶子节点的parent节点,并且说明当前节点是左(left)子树还是右子树(right)
  62. final Node parent = node.parent;
  63. if (parent.leftChild == node) {
  64. parent.leftChild = null;
  65. } else {
  66. parent.rightChild = null;
  67. }
  68. //重新平衡
  69. reBalance(parent);
  70. } else {
  71. replace(node);
  72. }
  73. return true;
  74. }
  75. /**
  76. * 插入
  77. *
  78. * @param data 数据
  79. * @return {@link Node}
  80. */
  81. public Node insert(int data) {
  82. if (root == null) {
  83. root = new Node(null, null, null, data, 1);
  84. return root;
  85. }
  86. return doInsert(data, root);
  87. }
  88. public void traverse() {
  89. doTraverse(root);
  90. System.out.println();
  91. }
  92. //==============================删================================
  93. private boolean isLeaf(Node node) {
  94. return node == null || node.leftChild == null && node.rightChild == null;
  95. }
  96. /**
  97. * 从AVL树中删除,可以通过把要删除的节点向下旋转成一个叶子节点,接着直接移除这个叶子节点来完成。
  98. * 因为在旋转成叶子节点期间最多有log n个节点被旋转,而每次AVL旋转耗费固定的时间,所以删除处理在整体上耗费O(log n) 时间。
  99. */
  100. private void replace(Node node) {
  101. //找到左子树中最大的节点 或者 是右子树中最小的节点
  102. final Node next = findNext(node);
  103. final Node parent = next.parent;
  104. //修改被替换节点的指向
  105. if (parent.leftChild == next) {
  106. if (next.leftChild == null) {
  107. parent.leftChild = null;
  108. } else {
  109. parent.leftChild = next.leftChild;
  110. next.leftChild.parent = parent;
  111. }
  112. } else if (parent.rightChild == next) {
  113. if (next.rightChild == null) {
  114. parent.rightChild = null;
  115. } else {
  116. parent.rightChild = next.rightChild;
  117. next.rightChild.parent = parent;
  118. }
  119. }
  120. //节点修改数据
  121. node.data = next.data;
  122. reBalance(node);
  123. }
  124. private Node findNext(Node node) {
  125. if (node.leftChild != null) {
  126. return doFindMaxNode(node.leftChild);
  127. }
  128. return doFindMinNode(node.rightChild);
  129. }
  130. private Node doFindMaxNode(Node node) {
  131. if (node.rightChild == null) {
  132. return node;
  133. } else {
  134. return doFindMaxNode(node.rightChild);
  135. }
  136. }
  137. private Node doFindMinNode(Node node) {
  138. if (node.leftChild == null) {
  139. return node;
  140. } else {
  141. return doFindMinNode(node.leftChild);
  142. }
  143. }
  144. //==============================查==================================
  145. private Node doFindNode(Integer data) {
  146. return data >= root.data ? findRight(root, data) : findLeft(root, data);
  147. }
  148. private Node findRight(Node root, Integer data) {
  149. if (root == null) {
  150. return null;
  151. }
  152. if (root.data.equals(data)) {
  153. return root;
  154. }
  155. if (data >= root.data) {
  156. //右子树
  157. return findRight(root.rightChild, data);
  158. } else {
  159. //左子树
  160. return findLeft(root.leftChild, data);
  161. }
  162. }
  163. private Node findLeft(Node root, Integer data) {
  164. if (root == null) {
  165. return null;
  166. }
  167. if (root.data.equals(data)) {
  168. return root;
  169. }
  170. if (data >= root.data) {
  171. //右子树
  172. return findRight(root.rightChild, data);
  173. } else {
  174. //左子树
  175. return findLeft(root.leftChild, data);
  176. }
  177. }
  178. //===========================是否AVL树===========================
  179. private boolean avl(Node node, boolean ok) {
  180. if (!ok) return false;
  181. if (node == null) {
  182. return true;
  183. }
  184. final boolean balance = isBalance(node);
  185. return avl(node.rightChild, balance) && avl(node.leftChild, balance);
  186. }
  187. //===========================插入===========================
  188. private Node doInsert(int data, Node parent) {
  189. //如果数据比当前节点小,则插入作为左子树
  190. if (data < parent.data) {
  191. if (parent.leftChild == null) {
  192. parent.leftChild = new Node(parent, null, null, data, 1);
  193. //修改高度 && 可能需要重新修正为平衡树
  194. changeNodeHeight(parent.leftChild);
  195. return parent.leftChild;
  196. } else {
  197. //存在左子的情况下,继续找左子
  198. return doInsert(data, parent.leftChild);
  199. }
  200. } else {
  201. //否则插入作为右子树
  202. if (parent.rightChild == null) {
  203. parent.rightChild = new Node(parent, null, null, data, 1);
  204. //修改高度 && 可能需要重新修正为平衡树
  205. changeNodeHeight(parent.rightChild);
  206. return parent.rightChild;
  207. } else {
  208. //存在右子的情况下,继续找右子
  209. return doInsert(data, parent.rightChild);
  210. }
  211. }
  212. }
  213. private void doReBalance(Node node) {
  214. if (isBalance(node)) {
  215. return;
  216. }
  217. node.height = Math.max(getHeight(node.leftChild), getHeight(node.rightChild)) + 1;
  218. if (leftNotBalance(node)) {
  219. //平衡因子,用来判断是旋转一次,还是旋转两次
  220. final int rotationFactor = rotationFactor(node.leftChild);
  221. if (rotationFactor < 0) {
  222. rr(node, node == root);
  223. } else {
  224. rl(node.leftChild, node == root);
  225. }
  226. } else {
  227. //右节点失去平衡 平衡因子,用来判断是旋转一次,还是旋转两次
  228. final int rotationFactor = rotationFactor(node.rightChild);
  229. if (rotationFactor > 0) {
  230. ll(node, node == root);
  231. } else {
  232. lr(node.rightChild, node == root);
  233. }
  234. }
  235. }
  236. /**
  237. * 重新平衡
  238. */
  239. private void reBalance(Node node) {
  240. //如果是root节点需要重新平衡
  241. if (node.parent == null) {
  242. doReBalance(node);
  243. return;
  244. }
  245. node.height = Math.max(getHeight(node.leftChild), getHeight(node.rightChild)) + 1;
  246. Node parent = node.parent;
  247. do {
  248. //递归修改parent节点的高度
  249. parent.height = Math.max(getHeight(parent.leftChild), getHeight(parent.rightChild)) + 1;
  250. //查看节点是否失去平衡
  251. if (!isBalance(parent)) {
  252. //左节点失去平衡
  253. if (leftNotBalance(parent)) {
  254. //平衡因子,用来判断是旋转一次,还是旋转两次
  255. final int rotationFactor = rotationFactor(parent.leftChild);
  256. if (rotationFactor < 0) {
  257. rr(parent, parent == root);
  258. } else {
  259. rl(parent.leftChild, parent == root);
  260. }
  261. } else {
  262. //右节点失去平衡 平衡因子,用来判断是旋转一次,还是旋转两次
  263. final int rotationFactor = rotationFactor(parent.rightChild);
  264. if (rotationFactor > 0) {
  265. ll(parent, parent == root);
  266. } else {
  267. lr(parent.rightChild, parent == root);
  268. }
  269. }
  270. }
  271. parent = parent.parent;
  272. } while (parent != null);
  273. }
  274. private void changeNodeHeight(Node node) {
  275. Node parent = node.parent;
  276. do {
  277. //递归修改parent节点的高度
  278. parent.height = Math.max(getHeight(parent.leftChild), getHeight(parent.rightChild)) + 1;
  279. //查看节点是否失去平衡
  280. if (!isBalance(parent)) {
  281. //左节点失去平衡(右旋)
  282. if (leftNotBalance(parent)) {
  283. //平衡因子(左子失去平衡,需要进行右旋,使用当前节点的左子树的根节点来判断平衡因子)
  284. //用来判断是旋转一次,还是旋转两次,如果小于0则进行一次右旋即可
  285. //否则进行一次左旋,一次右旋,左旋的作用是将数据规整为右旋的规则作用。方便做右旋操作
  286. final int rotationFactor = rotationFactor(parent.leftChild);
  287. if (rotationFactor < 0) {
  288. //右旋
  289. rr(parent, parent == root);
  290. } else {
  291. //
  292. rl(parent.leftChild, parent == root);
  293. }
  294. } else {
  295. //右节点失去平衡 平衡因子,用来判断是旋转一次,还是旋转两次 (左旋)
  296. //同上变的注释
  297. final int rotationFactor = rotationFactor(parent.rightChild);
  298. if (rotationFactor > 0) {
  299. ll(parent, parent == root);
  300. } else {
  301. lr(parent.rightChild, parent == root);
  302. }
  303. }
  304. }
  305. parent = parent.parent;
  306. } while (parent != null);
  307. }
  308. //先右旋 再左旋
  309. private void rl(Node node, boolean b) {
  310. final Node parent = node.parent;
  311. parent.leftChild = node.rightChild;
  312. node.rightChild.parent = parent;
  313. node.parent = parent.leftChild;
  314. node.rightChild = parent.rightChild == null ? null :
  315. parent.rightChild.leftChild;
  316. parent.leftChild.leftChild = node;
  317. node.height = Math.max(getHeight(null), getHeight(node.rightChild)) + 1;
  318. node.parent.height = Math.max(getHeight(node.parent), getHeight(node.parent)) + 1;
  319. rr(parent, b);
  320. }
  321. //先左旋 再右旋
  322. private void lr(Node node, boolean b) {
  323. final Node parent = node.parent;
  324. parent.rightChild = node.leftChild;
  325. node.leftChild.parent = parent;
  326. node.parent = parent.rightChild;
  327. node.leftChild = parent.leftChild == null ? null : parent.rightChild.rightChild;
  328. parent.rightChild.rightChild = node;
  329. node.height = Math.max(getHeight(null), getHeight(node.rightChild)) + 1;
  330. node.parent.height = Math.max(getHeight(node.parent), getHeight(node.parent)) + 1;
  331. ll(parent, b);
  332. }
  333. /**
  334. * 旋转因子,用来判断是旋转一次,还是旋转2次
  335. *
  336. * @param node 节点
  337. * @return int
  338. */
  339. private int rotationFactor(Node node) {
  340. final int left = getHeight(node.leftChild);
  341. final int right = getHeight(node.rightChild);
  342. return right - left;
  343. }
  344. /**
  345. * 左子树不平衡
  346. *
  347. * @param node 节点
  348. * @return boolean
  349. */
  350. private boolean leftNotBalance(Node node) {
  351. return (getHeight(node.rightChild) - getHeight(node.leftChild)) < 0;
  352. }
  353. private boolean rightBalance(Node node) {
  354. return (getHeight(node.rightChild) - getHeight(node.leftChild)) > 0;
  355. }
  356. /**
  357. * 判断当前节点是否平衡,abs(左子和右子的节点高度)<=1即可
  358. *
  359. * @param node 节点
  360. * @return boolean
  361. */
  362. private boolean isBalance(Node node) {
  363. if (node == null) {
  364. return true;
  365. }
  366. final int lh = getHeight(node.leftChild);
  367. final int rh = getHeight(node.rightChild);
  368. return Math.abs(rh - lh) <= 1;
  369. }
  370. //===========================遍历===========================
  371. private void doTraverse(Node node) {
  372. if (node == null) {
  373. System.out.println("么数据");
  374. return;
  375. }
  376. if (node.leftChild != null) {
  377. doTraverse(node.leftChild);
  378. }
  379. System.out.print(node.data + "(" + node.height + ")" + "\t");
  380. if (node.rightChild != null) {
  381. doTraverse(node.rightChild);
  382. }
  383. }
  384. //===========================旋转===========================
  385. /**
  386. * 右旋(某节点的左子树不平衡),注释同左旋
  387. * RightRight的缩写
  388. */
  389. private void rr(Node node, boolean isRoot) {
  390. final Node parent = node.parent;
  391. boolean isRightChild = false;
  392. if (parent != null) {
  393. if (parent.rightChild == node) {
  394. isRightChild = true;
  395. }
  396. }
  397. final Node leftChild = node.leftChild;
  398. //设置
  399. node.leftChild = leftChild.rightChild;
  400. if (leftChild.rightChild != null) {
  401. leftChild.rightChild.parent = node;
  402. }
  403. leftChild.parent = node.parent;
  404. if (parent != null) {
  405. if (isRightChild) {
  406. node.parent.rightChild = leftChild;
  407. } else {
  408. node.parent.leftChild = leftChild;
  409. }
  410. }
  411. leftChild.rightChild = node;
  412. node.parent = leftChild;
  413. if (isRoot) {
  414. root = leftChild;
  415. }
  416. node.height = Math.max(getHeight(node.leftChild), getHeight(node.rightChild)) + 1;
  417. leftChild.height = Math.max(getHeight(leftChild.leftChild), getHeight(leftChild.rightChild)) + 1;
  418. }
  419. /**
  420. * 左旋转(右子树不平衡) if是用来兼容各种节点有数据的情况
  421. * 假设当前的根节点为5,右子树的参考节点数据为 6 7,新插入节点为8的情况,
  422. * 6由于插入变为不平衡节点,需要旋转成根为7,左子为6,右子为8的情况
  423. *
  424. * <code>
  425. * 5 5 5
  426. * / \ / \ /\
  427. * 3 6 ====> 3 6 ====> 3 7
  428. * / \ / \ / / \
  429. * 1 7 1 7 1 6 8
  430. * \
  431. * 8
  432. * </code>
  433. *
  434. * @param node 6 失去平衡的节点
  435. */
  436. private void ll(Node node, boolean isRoot) {
  437. //获取当前节点的parent节点,用来修改parent的leftChild使用,因为一开始的leftChild旋转作为rightChild了
  438. //parent == 5
  439. final Node parent = node.parent;
  440. //判断当前节点是parent的左节点还是右节点,下文需要使用
  441. //如果当前节点是parent的左节点,旋转产生的新"根节点" 挂到parent的左子上
  442. //如果当前节点是parent的右节点,旋转产生的新"根节点" 挂到parent的右子上
  443. boolean isRightChild = false;
  444. if (parent != null) {
  445. if (parent.rightChild == node) {
  446. //6是5节点的右子树的根节点
  447. isRightChild = true;
  448. }
  449. }
  450. //获取右节点7
  451. final Node rightChild = node.rightChild;
  452. //将节点6的右孩子设置为7的左孩子,需要明白旋转方式,这里是旋转的理论基础
  453. node.rightChild = rightChild.leftChild;
  454. //如果节点7的左孩子不为空,将左孩子的parent指向6,这里完成了旋转的第一轮交接
  455. if (rightChild.leftChild != null) {
  456. rightChild.leftChild.parent = node;
  457. }
  458. //节点7的parent应用修改为节点6的parent5
  459. //节点5就指向了节点7
  460. rightChild.parent = node.parent;
  461. //如果parent为空,需要旋转出来的节点挂到parent节点下
  462. //如果6节点是左子,挂5的左子,如果6是右子就挂5的右子
  463. if (parent != null) {
  464. if (isRightChild) {
  465. node.parent.rightChild = rightChild;
  466. } else {
  467. node.parent.leftChild = rightChild;
  468. }
  469. }
  470. //节点7的左子设置为节点6
  471. rightChild.leftChild = node;
  472. //节点的6的parent指向节点7
  473. node.parent = rightChild;
  474. //这里的root判断是,由于Java的值传递
  475. if (isRoot) {
  476. root = rightChild;
  477. }
  478. //重新设置节点高度
  479. node.height = Math.max(getHeight(node.leftChild), getHeight(node.rightChild)) + 1;
  480. rightChild.height = Math.max(getHeight(rightChild.leftChild), getHeight(rightChild.rightChild)) + 1;
  481. }
  482. /**
  483. * 获取节点高度
  484. *
  485. * @param node 节点
  486. * @return int
  487. */
  488. private int getHeight(Node node) {
  489. return node == null ? 0 : node.height;
  490. }
  491. }

Node 类

  1. package io.github.chenshun00.web.tree;
  2. /**
  3. * @author luobo.cs@raycloud.com
  4. * @since 2021/8/14 10:26 上午
  5. */
  6. public class Node {
  7. public Node(Node parent, Node leftChild, Node rightChild, Integer data, Integer height) {
  8. this.leftChild = leftChild;
  9. this.rightChild = rightChild;
  10. this.parent = parent;
  11. this.data = data;
  12. this.height = height;
  13. }
  14. public Node leftChild;
  15. public Node rightChild;
  16. public Node parent;
  17. public Integer data;
  18. public Integer height;
  19. @Override
  20. public String toString() {
  21. return "Node:" + data;
  22. }
  23. }