一、先看一个需求

给你一个数列 (7, 3, 10, 12, 5, 1, 9),要求能够高效的完成对数据的查询和添加

二、解决方案分析

  1. 使用数组
    数组未排序, 优点:直接在数组尾添加,速度快。 缺点:查找速度慢
    数组排序,优点:可以使用二分查找,查找速度快,缺点:为了保证数组有序,在添加新数据时,找到插入位 置后,后面的数据需整体移动,速度慢。
  2. 使用链式存储-链表
    不管链表是否有序,查找速度都慢,添加数据速度比数组快,不需要数据整体移动。
  3. 使用二叉排序树

三、二叉排序树介绍

二叉排序树: BST: (Binary Sort(Search) Tree), 对于二叉排序树的任何一个非叶子节点,要求左子节点的值比当 前节点的值小,右子节点的值比当前节点的值大。

特别说明:如果有相同的值,可以将该节点放在左子节点或右子节点

比如针对前面的数据 (7, 3, 10, 12, 5, 1, 9)

对应的二叉排序树为:

树结构实际应用-二叉排序树 - 图1

四、二叉排序树创建和遍历

一个数组创建成对应的二叉排序树,并使用中序遍历二叉排序树,比如: 数组为 Array(7, 3, 10, 12, 5, 1, 9)

创建成对应的二叉排序树为:

树结构实际应用-二叉排序树 - 图2

五、二叉排序树的删除

二叉排序树的删除情况比较复杂,有下面三种情况需要考虑

  1. 删除叶子节点 (比如:2, 5, 9, 12)
  2. 删除只有一颗子树的节点 (比如:1)
  3. 删除有两颗子树的节点. (比如:7, 3,10 )

思路分析:

  1. //对删除结点的各种情况的思路分析:
  2. 第一种情况:删除叶子节点 (比如:2, 5, 9, 12)
  3. 思路
  4. (1) 需求先去找到要删除的结点 targetNode
  5. (2) 找到 targetNode 父结点 parent
  6. (3) 确定 targetNode parent 的左子结点 还是右子结点
  7. (4) 根据前面的情况来对应删除
  8. 左子结点 parent.left = null
  9. 右子结点 parent.right = null;
  10. 第二种情况: 删除只有一颗子树的节点 比如 1
  11. 思路
  12. (1) 需求先去找到要删除的结点 targetNode
  13. (2) 找到 targetNode 父结点 parent
  14. (3) 确定 targetNode 的子结点是左子结点还是右子结点
  15. (4) targetNode parent 的左子结点还是右子结点
  16. (5) 如果 targetNode 有左子结点
  17. 5. 1 如果 targetNode parent 的左子结
  18. parent.left = targetNode.left;
  19. 5.2 如果 targetNode parent 的右子结点
  20. parent.right = targetNode.left;
  21. (6) 如果 targetNode 有右子结点
  22. 6.1 如果 targetNode parent 的左子结点
  23. parent.left = targetNode.right;
  24. 6.2 如果 targetNode parent 的右子结点
  25. parent.right = targetNode.right
  26. 情况三 删除有两颗子树的节点. (比如:7, 310 )
  27. 思路
  28. (1) 需求先去找到要删除的结点 targetNode
  29. (2) 找到 targetNode 父结点 parent
  30. (3) targetNode 的右子树找到最小的结点
  31. (4) 用一个临时变量,将 最小结点的值保存 temp = 11
  32. (5) 删除该最小结点
  33. (6) targetNode.value = temp

六、代码实现:

  1. public class BinarySortTreeDemo {
  2. public static void main(String[] args) {
  3. int[] arr = {7, 3, 10, 12, 5, 1, 9, 2};
  4. BinarySortTree binarySortTree = new BinarySortTree();
  5. //循环的添加结点到二叉排序树
  6. for(int i = 0; i< arr.length; i++) {
  7. binarySortTree.add(new Node(arr[i]));
  8. }
  9. //中序遍历二叉排序树
  10. System.out.println("中序遍历二叉排序树~");
  11. binarySortTree.infixOrder(); // 1, 3, 5, 7, 9, 10, 12
  12. //测试一下删除叶子结点
  13. //binarySortTree.delNode(12);
  14. //binarySortTree.delNode(5);
  15. //binarySortTree.delNode(10);
  16. //binarySortTree.delNode(2);
  17. //binarySortTree.delNode(3);
  18. //binarySortTree.delNode(9);
  19. //binarySortTree.delNode(1);
  20. binarySortTree.delNode(7);
  21. System.out.println("root=" + binarySortTree.getRoot());
  22. System.out.println("删除结点后");
  23. binarySortTree.infixOrder();
  24. }
  25. }
  26. //创建二叉排序树
  27. class BinarySortTree {
  28. private Node root;
  29. public Node getRoot() {
  30. return root;
  31. }
  32. //查找要删除的结点
  33. public Node search(int value){
  34. if(root == null) {
  35. return null;
  36. } else {
  37. return root.search(value);
  38. }
  39. }
  40. //查找父结点
  41. public Node searchParent(int value) {
  42. if(root == null) {
  43. return null;
  44. } else {
  45. return root.searchParent(value);
  46. }
  47. }
  48. //编写方法:
  49. //1. 返回的 以 node 为根结点的二叉排序树的最小结点的值
  50. //2. 删除 node 为根结点的二叉排序树的最小结点
  51. /**
  52. *
  53. * @param node 传入的结点(当做二叉排序树的根结点)
  54. * @return 返回的 以 node 为根结点的二叉排序树的最小结点的值
  55. */
  56. public int delRightTreeMin(Node node) {
  57. Node target=node;
  58. //循环的查找左子节点,就会找到最小值
  59. while(target.left != null) {
  60. target = target.left;
  61. }
  62. //这时 target 就指向了最小结点
  63. //删除最小结点
  64. delNode(target.value);
  65. return target.value;
  66. }
  67. //删除结点
  68. public void delNode(int value) {
  69. if(root == null) {
  70. return;
  71. }else {
  72. //1.需求先去找到要删除的结点 targetNode
  73. Node targetNode = search(value);
  74. //如果没有找到要删除的结点
  75. if(targetNode == null) {
  76. return;
  77. }
  78. //如果我们发现当前这颗二叉排序树只有一个结点
  79. if(root.left == null && root.right == null) {
  80. root = null;
  81. return;
  82. }
  83. //去找到 targetNode 的父结点
  84. Node parent = searchParent(value);
  85. //如果要删除的结点是叶子结点
  86. if(targetNode.left == null && targetNode.right == null) {
  87. //判断 targetNode 是父结点的左子结点,还是右子结点
  88. if(parent.left != null && parent.left.value == value) { //是左子结点
  89. parent.left = null;
  90. } else if (parent.right != null && parent.right.value == value) {//是由子结点
  91. parent.right = null;
  92. }
  93. } else if (targetNode.left != null && targetNode.right != null) { //删除有两颗子树的节点
  94. int minVal = delRightTreeMin(targetNode.right);
  95. targetNode.value = minVal;
  96. } else { // 删除只有一颗子树的结点
  97. //如果要删除的结点有左子结点
  98. if(targetNode.left != null) {
  99. if(parent != null) {
  100. //如果 targetNode 是 parent 的左子结点
  101. if(parent.left.value == value) {
  102. parent.left = targetNode.left;
  103. } else { // targetNode 是 parent 的右子结点
  104. parent.right = targetNode.left;
  105. }
  106. } else {
  107. root = targetNode.left;
  108. }
  109. } else { //如果要删除的结点有右子结点
  110. if(parent != null) {
  111. //如果 targetNode 是 parent 的左子结点
  112. if(parent.left.value == value) {
  113. parent.left = targetNode.right;
  114. } else { //如果 targetNode 是 parent 的右子结点
  115. parent.right = targetNode.right;
  116. }
  117. } else {
  118. root = targetNode.right;
  119. }
  120. }
  121. }
  122. }
  123. }
  124. //添加结点的方法
  125. public void add(Node node) {
  126. if(root == null) {
  127. root = node;//如果 root 为空则直接让 root 指向 node
  128. } else {
  129. root.add(node);
  130. }
  131. }
  132. //中序遍历
  133. public void infixOrder() {
  134. if(root != null) {
  135. root.infixOrder();
  136. } else {
  137. System.out.println("二叉排序树为空,不能遍历");
  138. }
  139. }
  140. }
  141. //创建 Node 结点
  142. class Node {
  143. int value;
  144. Node left;
  145. Node right;
  146. public Node(int value) {
  147. this.value = value;
  148. }
  149. //查找要删除的结点
  150. /**
  151. *
  152. * @param value 希望删除的结点的值
  153. * @return 如果找到返回该结点,否则返回 null
  154. */
  155. public Node search(int value) {
  156. if(value == this.value) { //找到就是该结点
  157. return this;
  158. } else if(value < this.value) {//如果查找的值小于当前结点,向左子树递归查找
  159. //如果左子结点为空
  160. if(this.left == null) {
  161. return null;
  162. }
  163. return this.left.search(value);
  164. } else { //如果查找的值不小于当前结点,向右子树递归查找
  165. if(this.right == null) {
  166. return null;
  167. }
  168. return this.right.search(value);
  169. }
  170. }
  171. //查找要删除结点的父结点
  172. /**
  173. *
  174. * @param value 要找到的结点的值
  175. * @return 返回的是要删除的结点的父结点,如果没有就返回 null
  176. */
  177. public Node searchParent(int value) {
  178. //如果当前结点就是要删除的结点的父结点,就返回
  179. if((this.left != null && this.left.value == value) ||
  180. (this.right != null && this.right.value == value)) {
  181. return this;
  182. } else {
  183. //如果查找的值小于当前结点的值, 并且当前结点的左子结点不为空
  184. if(value < this.value && this.left != null) {
  185. return this.left.searchParent(value); //向左子树递归查找
  186. } else if (value >= this.value && this.right != null) {
  187. return this.right.searchParent(value); //向右子树递归查找
  188. } else {
  189. return null; // 没有找到父结点
  190. }
  191. }
  192. }
  193. @Override
  194. public String toString() {
  195. return "Node [value=" + value + "]";
  196. }
  197. //添加结点的方法
  198. //递归的形式添加结点,注意需要满足二叉排序树的要求
  199. public void add(Node node) {
  200. if(node == null) {
  201. return;
  202. }
  203. //判断传入的结点的值,和当前子树的根结点的值关系
  204. if(node.value < this.value) {
  205. //如果当前结点左子结点为 null
  206. if(this.left == null) {
  207. this.left = node;
  208. } else {
  209. //递归的向左子树添加
  210. this.left.add(node);
  211. }
  212. } else { //添加的结点的值大于 当前结点的值
  213. if(this.right == null) {
  214. this.right = node;
  215. } else {
  216. //递归的向右子树添加
  217. this.right.add(node);
  218. }
  219. }
  220. }
  221. //中序遍历
  222. public void infixOrder() {
  223. if(this.left != null) {
  224. this.left.infixOrder();
  225. }
  226. System.out.println(this);
  227. if(this.right != null) {
  228. this.right.infixOrder();
  229. }
  230. }
  231. }

七:提升

如果我们从左子树找到最大的结点,然后前面的思路完成

  1. /**
  2. * 以传进来的左节点为根节点,向右边节点遍历找到最大值
  3. *
  4. * @param leftNode
  5. * @return
  6. */
  7. public int deleteLeftTreeNode(Node leftNode) {
  8. // 向右遍历找到最大值
  9. Node targe = leftNode;
  10. if (targe.getRight() != null) {
  11. targe = targe.getRight();
  12. }
  13. // 删除最大值的节点
  14. delete(targe.getValue());
  15. return targe.getValue();
  16. }