目前为止,我们学的数据结构分为两种,数组和链表

数组存储分析

  • 优点:访问速度快,还可以通过二分查找增加速度
  • 缺点:如果要插入值需要整体移动,效率较低

链表存储分析

  • 优点:在一定程度上对数组存储上进行优化,插入删除变得十分遍历
  • 缺点:检索效率低,需要检索值的时候每次都要从头开始遍历。

现在我们既想插入的快点一点,又想遍历的快一点怎么办?
接下来我们引入一个新的数据结构:树,来解决这个问题

image.png
上边的树是一个有序树,7是树的根节点,3是7的左子节点,10是7的右子节点,7是10和3的父节点。
而有序树是什么呢,左子节点比父节点小,右子节点比父节点大。
仔细看上图会发现每一个节点都是如此。

而无序树的话,就随意存放了。

现在这个树是有序树,我们分析一下,如果我们需要找到5这个节点。
首先5比7小,那么5一定在7的左边,5再和3比较,比3大,那么5一定在3的右边,这样是不是有种二分查找的感觉了?没错,这样我们遍历起来也很快。而插入和删除的时候,我们只需要改变链接指向就可以了。这样我们删除和插入也会很快。

树的基础概念

image.png
树有很多种,最多只能有两个节点的树称为二叉树

  • 二叉树不存在度大于2的结点
  • 左右子树是有顺序的,即使某结点只有一棵子树,也要区分它是左子树还是右子树
  • 二叉树具有五种基本形态:空二叉树、只有一个根节点、只有左子树、只有右子树、左右子树都有
  • 二叉树第i层上至多有2^(i-1)个结点(i≥1)
  • 深度为k的二叉树至多有2^k - 1个结点(k≥1),此时为满二叉树
  • 结点拥有的子树数称为结点的度,度为0的结点称为叶子结点
  • 对任何一棵二叉树T,如果其叶子结点数为n0,度为2的结点数为n2,则n0 = n2 + 1。这个的推导:设结点数为n,可以知道结点间连接线数为n-1。于是有两个式子:n-1 = n1 + 2*n2 和 n = n0 + n1 +n2,联合解出n0 = n2 + 1
  • 若设二叉树的深度为k,除第 k 层外,其它各层 (1~k-1) 的结点数都达到最大个数,第k 层所有的结点都连续集中在最左边,这就是完全二叉树。

image.png

  • 对于完全二叉树,在有左右子结点的情况下,设根结点的编号是n(这个编号从1开始),则左孩子的编号是2n,右孩子的编号是2n+1

    二叉树的前中后序遍历

    image.png
    对图中的树进行分析
    前序遍历:12354
    中序遍历:21534
    后序遍历:25431

代码实现

  1. public class Node {
  2. int id;
  3. Node left;
  4. Node right;
  5. @Override
  6. public String toString() {
  7. return "Node{" +
  8. "id=" + id +
  9. '}';
  10. }
  11. public Node(int id) {
  12. this.id = id;
  13. }
  14. }
  1. public class BinaryTreeDemo {
  2. public static void main(String[] args) {
  3. BinaryTree binaryTree = new BinaryTree();
  4. Node root = new Node(1);
  5. Node node2 = new Node(2);
  6. Node node3 = new Node(3);
  7. Node node4 = new Node(4);
  8. Node node5 = new Node(5);
  9. root.left = node2;
  10. root.right = node3;
  11. node3.left = node5;
  12. node3.right = node4;
  13. binaryTree.setRoot(root);
  14. // binaryTree.preOrder(root);
  15. // binaryTree.midOrder(root);
  16. binaryTree.postOrder(root);
  17. }
  18. }
  19. class BinaryTree {
  20. //根节点
  21. private Node root;
  22. public void setRoot(Node root) {
  23. this.root = root;
  24. }
  25. //前序遍历
  26. public void preOrder(Node root) {
  27. //首先输出父节点
  28. System.out.println(root);
  29. //判断左节点是否为空
  30. if (root.left != null) {
  31. preOrder(root.left);
  32. }
  33. if (root.right != null) {
  34. preOrder(root.right);
  35. }
  36. }
  37. //中序遍历
  38. public void midOrder(Node root) {
  39. //判断左节点是否为空
  40. if (root.left != null) {
  41. midOrder(root.left);
  42. }
  43. //输出父节点
  44. System.out.println(root);
  45. //判断右节点
  46. if (root.right != null) {
  47. midOrder(root.right);
  48. }
  49. }
  50. //后序遍历
  51. public void postOrder(Node root) {
  52. //判断左节点是否为空
  53. if (root.left != null) {
  54. postOrder(root.left);
  55. }
  56. //判断右节点
  57. if (root.right != null) {
  58. postOrder(root.right);
  59. }
  60. //输出父节点
  61. System.out.println(root);
  62. }
  63. }

线索化二叉树

image.png
图中我们可以发现,它的中序遍历顺序为 8,3,10,1,14,6
但叶子节点8,10,14的左右指针和 节点6的右指针都没用上。
为了不让它空闲,充分利用空间
我们可以让它们的 左指针指向前驱节点,右指针指向后继节点
image.png
这样我们可以快速得到遍历顺序。
这样的二叉树,我们称之为线索化二叉树。

代码实现

为了能够区分一个节点的左指针到底指向的是前驱节点还是左子节点,我们有必要用一个变量来区分一下

  1. public class Node {
  2. int id;
  3. Node left;
  4. Node right;
  5. //为了在线索化二叉树中区分
  6. //0表示子节点,1表示前驱或后继结点
  7. int leftType;
  8. int rightType;
  9. @Override
  10. public String toString() {
  11. return "Node{" +
  12. "id=" + id +
  13. '}';
  14. }
  15. public Node(int id) {
  16. this.id = id;
  17. }
  18. }

首先我们要对二叉树进行线索化。
以中序为例,不明白代码的,debug或者画图就会了解。

树 - 图7

然后我们之前的中序遍历方式毫无疑问已经不能用了,所以我们必须重新相出一个遍历方法从根节点开始

  • 先找到被线索化的节点curNode,遍历循环 curNode = curNode.left;
  • 如果找不到线索化的节点,输出当前节点
  • 如果 curNode.rightType = 1的话,一直遍历输出右指针类型为0
  • 现在rightType = 0了,我们让curNode = curNode.right ,再次回到第一步循环,直到curNode = null

我们对着图先来一遍过程:
首先找到了节点8,发现这个是被线索化的节点,curNode=8,那么我们一直遍历它的后继节点->3,这个时候我们发现3并不是被线索化的节点。那么我们只能让curNode = 3了,我们发现它没指向后继节点,那么我们就让curNode = curNode.right
现在curNode = 10了,我们继续来循环,遍历left发现,leftType = 1,那没事了,我们来遍历rightType = 1的时候。输出了1,发现1也没后继,那么curNode = curNode.right,
curNode = 6,发现6也不是被线索化的节点,我们就遍历left找到了14.然后就完事了。

  1. public class ThreadedBinaryTreeDemo {
  2. //线索化二叉树的实现
  3. public static void main(String[] args) {
  4. ThreadedBinaryTree threadedBinaryTree = new ThreadedBinaryTree();
  5. Node root = new Node(1);
  6. Node node3 = new Node(3);
  7. Node node8 = new Node(8);
  8. Node node10 = new Node(10);
  9. Node node14 = new Node(14);
  10. Node node6 = new Node(6);
  11. root.left = node3;
  12. root.right = node6;
  13. node3.left = node8;
  14. node3.right = node10;
  15. node6.left = node14;
  16. threadedBinaryTree.setRoot(root);
  17. // threadedBinaryTree.midOrder(root);
  18. threadedBinaryTree.threadedNodes();
  19. threadedBinaryTree.threadedList();
  20. }
  21. }
  22. class ThreadedBinaryTree {
  23. //根节点
  24. private Node root;
  25. //为了实现线索化,需要创建一个指向前驱结点的一个指针
  26. private Node pre = null;
  27. public void setRoot(Node root) {
  28. this.root = root;
  29. }
  30. public void threadedNodes() {
  31. this.threadedNodes(root);
  32. }
  33. /**
  34. * 对当前节点进行线索化
  35. * 中序线索化为例
  36. * @param node
  37. */
  38. public void threadedNodes(Node node) {
  39. //如果node == null,无法线索化
  40. if (node == null){
  41. return;
  42. }
  43. //先线索化左子树
  44. threadedNodes(node.left);
  45. //线索化当前节点
  46. //处理前驱节点
  47. if (node.left == null) {
  48. //指向前驱节点
  49. node.left = pre;
  50. //修改当前节点的左指针类型
  51. node.leftType = 1;
  52. }
  53. //指向后继节点(实际上是在第一次回溯的时候才开始进行)
  54. //第一次 pre = null,当第一次回溯的时候,这里pre变成了前驱节点
  55. if (pre != null && pre.right == null) {
  56. pre.right = node;
  57. pre.rightType = 1;
  58. }
  59. //最最重要的一条
  60. pre = node;
  61. //线索化右子树
  62. threadedNodes(node.right);
  63. }
  64. //遍历线索化二叉树的方法(中序为例)
  65. public void threadedList() {
  66. //定义一个变量,存储当前遍历的节点
  67. Node curNode = root;
  68. while (curNode != null) {
  69. //循环找到被线索化的节点 如果节点的leftType == 1,说明是线索化的有效节点
  70. while (curNode.leftType == 0) {
  71. curNode = curNode.left;
  72. }
  73. System.out.println(curNode);
  74. //如果当前节点的右指针指向的是后继节点,那么就一直输出。
  75. while (curNode.rightType == 1) {
  76. curNode = curNode.right;
  77. System.out.println(curNode);
  78. }
  79. curNode = curNode.right;
  80. }
  81. }
  82. }

哈夫曼树(最优二叉树)

哈夫曼树,也被叫做最优二叉树,它的带权路径的值(WPL)为最小。所谓树的带权路径长度,就是树中所有的叶结点的权值乘上其到根结点的路径长度。权值越大的节点,离根节点越近。
这样说可能有点晦涩

举个例子:

image.png
wpl = 13 2 + 7 2 + 8 2 + 3 2
13 为叶子节点的值,2为路径长度(根节点到该叶子节点的长度)。但是这并不是哈夫曼树

权值越大的节点,离根节点越近

image.png这样才是最优二叉树

现在一组数据,我们将它变为二叉树
13, 7, 8, 3, 29, 6, 1
从中找出两个最小的 值(可以一开始先排好序)组成一个二叉树,根节点的值为 两个最小值的和,然后将这个值放进数组中
image.png
现在数组变为了 13, 7, 8, 29, 6, 4
再次从中找两个最小值,4,6,组成一个二叉树。将4 + 6 =10放入到数组中。
image.png
现在数组变为13, 7, 8, 29,10
再次找出最小值 7 和 8,我们发现与我们上边组成的树毫无关系,那么我们只能新组一个树。
然后将15放入数组
image.png
现在数组变为13,29,10,15
两个最小值为13和10,现在我们将13和10组成树,23放入到数组中
image.png
现在数组变为 29,15,23
两个最小值为15和23,38放进数组
image.png
数组只剩下29和38了,我们放入树中,哈夫曼树完成!
image.png

代码实现

  1. public class HuffmanTree {
  2. //哈夫曼树
  3. public static void main(String[] args) {
  4. int[] arr = {13, 7, 8, 3, 29, 6, 1};
  5. //创建哈夫曼树
  6. HuffmanNode root = createHuffman(arr);
  7. preOrder(root);
  8. }
  9. //创建哈夫曼树
  10. private static HuffmanNode createHuffman(int[] arr) {
  11. //首先转化成list方便操作
  12. List<HuffmanNode> data = new LinkedList<>();
  13. for (int i = 0; i < arr.length; i++) {
  14. data.add(new HuffmanNode(arr[i]));
  15. }
  16. while (data.size() > 1) {
  17. //进行排序(也可以自己手写)
  18. data = data.stream().sorted((o1, o2) -> o1.val - o2.val).collect(Collectors.toList());
  19. //去除两个最小的值
  20. HuffmanNode leftNode = data.get(0);
  21. HuffmanNode rightNode = data.get(1);
  22. //生成树
  23. HuffmanNode parent = new HuffmanNode(leftNode.val + rightNode.val);
  24. parent.left = leftNode;
  25. parent.right = rightNode;
  26. data.remove(leftNode);
  27. data.remove(rightNode);
  28. data.add(parent);
  29. }
  30. //返回根节点。
  31. return data.get(0);
  32. }
  33. public static void preOrder(HuffmanNode root) {
  34. //首先输出父节点
  35. System.out.println(root);
  36. //判断左节点是否为空
  37. if (root.left != null) {
  38. preOrder(root.left);
  39. }
  40. if (root.right != null) {
  41. preOrder(root.right);
  42. }
  43. }
  44. }
  45. class HuffmanNode {
  46. int val;
  47. HuffmanNode left;
  48. HuffmanNode right;
  49. public HuffmanNode(int val) {
  50. this.val = val;
  51. }
  52. @Override
  53. public String toString() {
  54. return "HuffmanNode{" +
  55. "val=" + val +
  56. '}';
  57. }
  58. }

二叉排序树(BST)

image.png
二叉排序树的定义和创建都非常的简单,他的麻烦之处在于删除。
仔细观察二叉排序树,会发现它在查找的时候就有着二分的性质,这点让它既能快速的增删,也能快速的查找。

创建就没什么好说的了。比根节点大就放右边,小就放左边。
我们来说一下删除。
删除分三种情况

一种是删除叶子节点。比如说上图的2,5,9,12
删除叶子节点好说,我们只需要找到它的父节点,然后判断它是父节点的左子树还是右子树,让对应的left和right指针为null就行了。

删除只有一个子节点的节点。
比如说上图中的节点 1
我们首先要判断要删除的节点 (这里我们叫做targetNode) 是有左子树还是右子树
有左子树的情况
我们再判断targetNode是父节点(parentNode)的左子树还是右子树,然后对应的
parentNode.left = targetNode.left 或 parentNode.right = targetNode.left;
有右子树的情况则反之。
parentNode.left = targetNode.right 或 parentNode.right = targetNode.right;

最后一种情况就是,被删除的节点targetNode 有两个子节点。比如,3,7,10
这个时候,我们的做法应该是。
找出右子树中值最小的那个节点
也就是一直遍targetNode.right 的左边,直到找到 targetNode.right.left = null,targetNode的变成这个最小节点的值。

我们也可以选择找出左子树中最大的节点。

代码实现

  1. public class BinarySortTreeDemo {
  2. public static void main(String[] args) {
  3. int[] arr = {7, 3, 10, 12, 5, 1, 9, 2};
  4. BinarySortTree tree = new BinarySortTree();
  5. for (int i = 0; i < arr.length; i++) {
  6. Node node = new Node(arr[i]);
  7. tree.add(node);
  8. }
  9. tree.midOrder(tree.root);
  10. tree.delNode(2);
  11. tree.delNode(5);
  12. tree.delNode(9);
  13. tree.delNode(12);
  14. tree.delNode(7);
  15. tree.delNode(3);
  16. tree.delNode(10);
  17. tree.delNode(1);
  18. tree.midOrder(tree.root);
  19. }
  20. }
  21. class BinarySortTree {
  22. Node root;
  23. public void add(Node node) {
  24. if (root == null) {
  25. root = node;
  26. }else {
  27. if (node == null) {
  28. return;
  29. }
  30. Node curNode = root;
  31. while (curNode != null) {
  32. if (curNode.id > node.id) {
  33. //说明curNode比新增节点大,新增节点应该在当前节点的左边
  34. if (curNode.left == null) {
  35. //如果当前节点的左子树为null,那么我们可以直接加入到curNode左边。
  36. curNode.left = node;
  37. return;
  38. }
  39. curNode = curNode.left;
  40. }else {
  41. if (curNode.right == null) {
  42. //如果当前节点的右子树为null,那么我们可以直接加入到curNode右边。
  43. curNode.right = node;
  44. return;
  45. }
  46. curNode = curNode.right;
  47. }
  48. }
  49. }
  50. }
  51. //中序遍历
  52. public void midOrder(Node node) {
  53. if (root == null) {
  54. return;
  55. }
  56. if (node.left != null) {
  57. midOrder(node.left);
  58. }
  59. System.out.println(node);
  60. if (node.right != null) {
  61. midOrder(node.right);
  62. }
  63. }
  64. /**
  65. * 查找要删除的节点
  66. *
  67. * @param value 希望删除的节点的值
  68. */
  69. public Node search(int value) {
  70. return this.search(root, value);
  71. }
  72. public Node search(Node node,int value) {
  73. if (node.id == value) {
  74. return node;
  75. } else if (value < node.id) {
  76. if (node.left != null) {
  77. return search(node.left, value);
  78. }
  79. return null;
  80. } else {
  81. if (node.right != null) {
  82. return search(node.right, value);
  83. }
  84. return null;
  85. }
  86. }
  87. //查找要删除节点的父节点
  88. public Node searchParent(Node node, int value) {
  89. //左子树不为null,并且左子树值相等 or(右子树不为null,右子树值相等)
  90. if ((node.left != null && node.left.id == value) ||
  91. (node.right != null && node.right.id == value)){
  92. return node;
  93. }else {
  94. //比当前节点值小,去递归左子树找,否则去右子树
  95. if (value < node.id && node.left != null) {
  96. return searchParent(node.left, value);
  97. }else if (value > node.id && node.right != null) {
  98. return searchParent(node.right, value);
  99. }
  100. //都不满足返回null
  101. return null;
  102. }
  103. }
  104. public void delNode(int value) {
  105. if (root != null) {
  106. //当前树只有根节点的情况,我们可以直接置空
  107. if (root.left == null && root.right ==null) {
  108. root = null;
  109. return;
  110. }
  111. Node targetNode = search(value);
  112. //没找到要和删除的节点
  113. if (targetNode == null) {
  114. return;
  115. }
  116. //找到要删除节点的父节点
  117. Node parentNode = searchParent(root,value);
  118. //如果当前节点为叶子节点
  119. if (targetNode.left == null && targetNode.right == null){
  120. //判断当前节点是父节点左子树还是右子树
  121. if (parentNode.left != null && value == parentNode.left.id) {
  122. parentNode.left = null;
  123. }else if (parentNode.right != null && value == parentNode.right.id){
  124. parentNode.right = null;
  125. }
  126. }else if (targetNode.left != null && targetNode.right != null) {
  127. //当前节点有两个子节点,即有两个左右子树
  128. int min = delRightTreeMin(targetNode.right);
  129. targetNode.id = min;
  130. }else {
  131. //当前节点有一个子节点
  132. //如果要删除的节点有左子树
  133. if (targetNode.left != null) {
  134. //如果父节点为null,那么该节点为根节点
  135. if (parentNode != null) {
  136. //并且当前节点 为父节点的左子树
  137. if (value == parentNode.left.id) {
  138. parentNode.left = targetNode.left;
  139. }else {
  140. //当前节点为父节点的右子节点
  141. parentNode.right = targetNode.left;
  142. }
  143. }else {
  144. root = targetNode.left;
  145. }
  146. }else {
  147. if (parentNode != null) {
  148. //要删除的节点有右子节点
  149. //并且当前节点 为父节点的左子树
  150. if (value == parentNode.left.id) {
  151. parentNode.left = targetNode.right;
  152. }else {
  153. //当前节点为父节点的右子节点
  154. parentNode.right = targetNode.right;
  155. }
  156. }else {
  157. root = targetNode.right;
  158. }
  159. }
  160. }
  161. }
  162. }
  163. /**
  164. * 查找右子树中的最小节点
  165. * @param node
  166. * @return
  167. */
  168. public int delRightTreeMin(Node node) {
  169. Node target = node;
  170. //循环的查找左子节点,就会找到最小值
  171. while (target.left != null) {
  172. target = target.left;
  173. }
  174. //这时target已经指向了最小节点,我们需要把这个节点给删掉
  175. delNode(target.id);
  176. return target.id;
  177. }
  178. }

现在我们已经很清楚如何对一个二叉排序树进行基本操作了。
但是如果现在有一组数据为1,2,3,4,5,那么我们来变成一个二叉排序树看看
image.png
这个时候我们会发现,这个树变回单链表了对吧?它的查找速度毫无疑问变慢了,甚至因为一些多余的操作,比原来的单链表还要慢,完全没有发挥出我们原有的二叉排序树的特性,我们该这么办?
为了解决这个问题,我们要引入二叉平衡树。

二叉平衡树(AVL)

image.png
AVL是对二叉排序树的一种改进。
当它的两个子树的高度差值大于1的时候,就会出现旋转。

左旋

如果右子树的高度 - 左子树的高度 > 1,那么会进行左旋转,降低右子树的高度。
我们来分析一波数据,4, 3, 6, 5, 7, 8
image.png
毫无疑问,根节点左子树的高度为3 - 根节点左子树高度为1 = 2 > 1,这个时候我们要进行左旋转。
左旋转步骤

  • 1-首先创建一个新节点,值为当前节点的值
  • 2-新节点left 指向当前节点的left
  • 3-新节点的right 指向当前节点的右子树的左子树
  • 4-当前节点的值替换成右子树的值
  • 5-当前节点右子树设置成当前节点的右子树的右子树
  • 6-当前节点的左子树设置成新的节点

步骤如下图:
image.png

右旋

如果左子树的高度 - 右子树的高度 > 1,那么会进行右旋转,降低左子树的高度。
一组数据:10, 12, 8, 9, 7, 6
image.png
毫无疑问,左子树高度3 - 右子树高度1 = 2 > 1,要进行右旋转
右旋转的步骤:

  • 1-首先创建一个新节点,值为当前节点的值
  • 2-新节点right 指向当前节点的right
  • 3-新节点left 指向当前节点的left的right
  • 4-当前节点的值替换成左子树的值
  • 5-当前节点左子树设置成当前节点的左子树的左子树
  • 6-当前节点的右子树设置成新的节点

步骤如图:
image.png

我们我们在进行转为平衡树的时候,只需要一次左旋转和一次右旋转,这样就能解决问题了吗?
并不,有的时候,我们需要双旋转,即一次左旋加右旋(或一次右旋加左旋)

双旋

一组数据:10, 11, 7, 6, 8, 9
我们来看看它的树结构,会发现确实左子树高度 > 右子树高度,于是我们进行了一次右旋,发现变成了右子树高度大于左子树高度了,这并不是我们所期望的二叉树
image.png
这种情况怎么办呢?
我们在进行右旋之前,先要判断根节点的左子树的情况。
如果左子树的右子树高度 > 左子树的左子树高度,那么我们要对根节点的左子树进行一次左旋。
image.png
同理在进行左旋之前
我们要判断根节点右子树的情况
如果根节点右子树的左子树的高度 > 右子树的右子树的高度,那么我们要先对根节点的右子树右旋
这里就不画图了,根上边步骤差不多。

代码实现

  1. public class AVLTreeDemo {
  2. public static void main(String[] args) {
  3. //左旋转
  4. //int[] arr = {4, 3, 6, 5, 7, 8};
  5. //右旋转
  6. //int[] arr = {10, 12, 8, 9, 7, 6};
  7. //这组数据会发现,单旋转并不能让我们的树变为平衡二叉树
  8. int[] arr = {10, 11, 7, 6, 8, 9};
  9. AVLTree avlTree = new AVLTree();
  10. for (int i = 0; i < arr.length; i++) {
  11. avlTree.add(new AVLNode(arr[i]));
  12. }
  13. avlTree.midOrder(avlTree.root);
  14. System.out.println("当前根节点为" + avlTree.root);
  15. System.out.println("树的高度" + avlTree.root.height());
  16. System.out.println("左子树的高度" + avlTree.root.leftHeight());
  17. System.out.println("右子树的高度" + avlTree.root.rightHeight());
  18. }
  19. }
  20. class AVLTree {
  21. AVLNode root;
  22. /**
  23. * 因为右子树高度 - 左子树高度 > 1
  24. * 左旋转 变为平衡二叉树
  25. *
  26. * @param node 在非双旋(左右都要旋转)的情况下,为root
  27. */
  28. public void leftRotate(AVLNode node) {
  29. //首先创建一个新节点,值为当前节点的值
  30. AVLNode newNode = new AVLNode(node.id);
  31. //新节点left 指向当前节点的left
  32. newNode.left = node.left;
  33. //新节点的right 指向当前节点的右子树的左子树
  34. newNode.right = node.right.left;
  35. //当前节点的值替换成右子树的值
  36. node.id = node.right.id;
  37. //当前节点右子树设置成当前节点的右子树的右子树
  38. node.right = node.right.right;
  39. //当前节点的左子树设置成新的节点
  40. node.left = newNode;
  41. }
  42. /**
  43. * 左子树高度 - 右子树高度 > 1
  44. * 右旋转 变为平衡二叉树
  45. * @param node 在非双旋的情况下,为root
  46. */
  47. public void rightRotate(AVLNode node) {
  48. //首先创建一个新节点,值为当前节点的值
  49. AVLNode newNode = new AVLNode(node.id);
  50. //新节点right 指向当前节点的right
  51. newNode.right = node.right;
  52. //新节点left 指向当前节点的left的right
  53. newNode.left = node.left.right;
  54. //当前节点的值替换成左子树的值
  55. node.id = node.left.id;
  56. node.left = node.left.left;
  57. node.right = newNode;
  58. }
  59. //以下代码就是二叉排序数的代码,add方法要修改
  60. public void add(AVLNode node) {
  61. if (root == null) {
  62. root = node;
  63. return;
  64. }
  65. if (node == null) {
  66. return;
  67. }
  68. AVLNode curNode = root;
  69. while (curNode != null) {
  70. if (curNode.id > node.id) {
  71. //说明curNode比新增节点大,新增节点应该在当前节点的左边
  72. if (curNode.left == null) {
  73. //如果当前节点的左子树为null,那么我们可以直接加入到curNode左边。
  74. curNode.left = node;
  75. break;
  76. }
  77. curNode = curNode.left;
  78. }else {
  79. if (curNode.right == null) {
  80. //如果当前节点的右子树为null,那么我们可以直接加入到curNode右边。
  81. curNode.right = node;
  82. break;
  83. }
  84. curNode = curNode.right;
  85. }
  86. }
  87. int leftHeight = root.leftHeight();
  88. int rightHeight = root.rightHeight();
  89. //判断是否需要左旋转
  90. if (rightHeight - leftHeight > 1) {
  91. System.out.println("左旋转");
  92. //如果右子树的左子树高度大于右子树的右子树高度的话,那么我们还要进行一次右旋转
  93. if (root.right != null && root.right.leftHeight() > root.right.rightHeight()) {
  94. //右节点
  95. System.out.println("右子树右旋转");
  96. leftRotate(root.right);
  97. }
  98. leftRotate(root);
  99. return;
  100. }
  101. //判断是否需要右旋转
  102. if (leftHeight - rightHeight > 1) {
  103. System.out.println("右旋转");
  104. //如果左子树的右子树高度大于左子树的左子树高度的话,那么我们还要进行一次左旋转
  105. if (root.left != null && root.left.rightHeight() > root.left.leftHeight()) {
  106. //左节点
  107. System.out.println("左子树左旋转");
  108. leftRotate(root.left);
  109. }
  110. rightRotate(root);
  111. }
  112. }
  113. //中序遍历
  114. public void midOrder(AVLNode node) {
  115. if (root == null) {
  116. return;
  117. }
  118. if (node.left != null) {
  119. midOrder(node.left);
  120. }
  121. System.out.println(node);
  122. if (node.right != null) {
  123. midOrder(node.right);
  124. }
  125. }
  126. /**
  127. * 查找要删除的节点
  128. *
  129. * @param value 希望删除的节点的值
  130. */
  131. public AVLNode search(int value) {
  132. return this.search(root, value);
  133. }
  134. public AVLNode search(AVLNode node,int value) {
  135. if (node.id == value) {
  136. return node;
  137. } else if (value < node.id) {
  138. if (node.left != null) {
  139. return search(node.left, value);
  140. }
  141. return null;
  142. } else {
  143. if (node.right != null) {
  144. return search(node.right, value);
  145. }
  146. return null;
  147. }
  148. }
  149. //查找要删除节点的父节点
  150. public AVLNode searchParent(AVLNode node, int value) {
  151. //左子树不为null,并且左子树值相等 or(右子树不为null,右子树值相等)
  152. if ((node.left != null && node.left.id == value) ||
  153. (node.right != null && node.right.id == value)){
  154. return node;
  155. }else {
  156. //比当前节点值小,去递归左子树找,否则去右子树
  157. if (value < node.id && node.left != null) {
  158. return searchParent(node.left, value);
  159. }else if (value > node.id && node.right != null) {
  160. return searchParent(node.right, value);
  161. }
  162. //都不满足返回null
  163. return null;
  164. }
  165. }
  166. public void delNode(int value) {
  167. if (root != null) {
  168. //当前树只有根节点的情况,我们可以直接置空
  169. if (root.left == null && root.right ==null) {
  170. root = null;
  171. return;
  172. }
  173. AVLNode targetNode = search(value);
  174. //没找到要和删除的节点
  175. if (targetNode == null) {
  176. return;
  177. }
  178. //找到要删除节点的父节点
  179. AVLNode parentNode = searchParent(root,value);
  180. //如果当前节点为叶子节点
  181. if (targetNode.left == null && targetNode.right == null){
  182. //判断当前节点是父节点左子树还是右子树
  183. if (parentNode.left != null && value == parentNode.left.id) {
  184. parentNode.left = null;
  185. }else if (parentNode.right != null && value == parentNode.right.id){
  186. parentNode.right = null;
  187. }
  188. }else if (targetNode.left != null && targetNode.right != null) {
  189. //当前节点有两个子节点,即有两个左右子树
  190. int min = delRightTreeMin(targetNode.right);
  191. targetNode.id = min;
  192. }else {
  193. //当前节点有一个子节点
  194. //如果要删除的节点有左子树
  195. if (targetNode.left != null) {
  196. //如果父节点为null,那么该节点为根节点
  197. if (parentNode != null) {
  198. //并且当前节点 为父节点的左子树
  199. if (value == parentNode.left.id) {
  200. parentNode.left = targetNode.left;
  201. }else {
  202. //当前节点为父节点的右子节点
  203. parentNode.right = targetNode.left;
  204. }
  205. }else {
  206. root = targetNode.left;
  207. }
  208. }else {
  209. if (parentNode != null) {
  210. //要删除的节点有右子节点
  211. //并且当前节点 为父节点的左子树
  212. if (value == parentNode.left.id) {
  213. parentNode.left = targetNode.right;
  214. }else {
  215. //当前节点为父节点的右子节点
  216. parentNode.right = targetNode.right;
  217. }
  218. }else {
  219. root = targetNode.right;
  220. }
  221. }
  222. }
  223. }
  224. }
  225. /**
  226. * 查找右子树中的最小节点
  227. * @param node
  228. * @return
  229. */
  230. public int delRightTreeMin(AVLNode node) {
  231. AVLNode target = node;
  232. //循环的查找左子节点,就会找到最小值
  233. while (target.left != null) {
  234. target = target.left;
  235. }
  236. //这时target已经指向了最小节点,我们需要把这个节点给删掉
  237. delNode(target.id);
  238. return target.id;
  239. }
  240. }
  241. class AVLNode {
  242. int id;
  243. AVLNode left;
  244. AVLNode right;
  245. @Override
  246. public String toString() {
  247. return "AVLNode{" +
  248. "id=" + id +
  249. '}';
  250. }
  251. public AVLNode(int id) {
  252. this.id = id;
  253. }
  254. //返回以当前节点为根节点的树的高度(当前节点+子树有多高)
  255. public int height() {
  256. return Math.max(left == null ? 0 : left.height(),right == null ? 0 : right.height()) + 1;
  257. }
  258. //返回左子树的高度
  259. public int leftHeight() {
  260. if (left == null) {
  261. return 0;
  262. }
  263. return left.height();
  264. }
  265. public int rightHeight() {
  266. if (right == null) {
  267. return 0;
  268. }
  269. return right.height();
  270. }
  271. }