二分搜索树

什么是树结构
二分搜索树 - 图1
当你把上面的图倒过来看,就像是一棵树,所以我们把这种结构称为是树结构。那为什么要使用树结构,因为树结构在生活中很常见,如文件夹的组织方式,又或者如公司职能的组织方式,这些都是树结构的例子。为什么会使用树结构呢? 原因就是因为高效。

概念

同链表一样,它也是一种动态的数据结构,链表中的节点是指向一个节点,而二叉树是指向两个节点,我们把这两个节点称为左子树和右子树,又或者称为左孩子和右孩子。如下图表示的就是二叉树
二分搜索树 - 图2

  1. class Node {
  2. E e;
  3. Node left;
  4. Node right;
  5. }
  • 根节点
    • 最顶部的那个节点,如上图中28就是根节点
    • 二叉树具有唯一的一个根节点
  • 叶子节点
    • 没有孩子的节点,如上图的最后一行都是叶子节点
  • 二叉树的每个节点最多有两个孩子,最多有一个父亲

那是什么是二分搜索树,首先二分搜索树是二叉树,它满足这样的特点,对于每个节点

  • 大于左子树所有节点的值
  • 小于右子树所有节点的值

二分搜索树 - 图3
可以验算,上面的这棵树满足二分搜索树的性质,所以这棵树是二分搜索树。下面我们来实现二分搜索树中节点有关代码

  1. public class BST<E extends Comparable<E>> {
  2. private class Node {
  3. public E e;
  4. public Node left;
  5. public Node right;
  6. public Node(E e) {
  7. this.e = e;
  8. left = null;
  9. right = null;
  10. }
  11. @Override
  12. public String toString() {
  13. return e.toString();
  14. }
  15. }
  16. //根节点
  17. private Node root;
  18. //树中元素的个数
  19. private int size;
  20. public BST() {
  21. root = null;
  22. size = 0;
  23. }
  24. public int size() {
  25. return size;
  26. }
  27. public boolean isEmpty() {
  28. return size == 0;
  29. }
  30. }

实现

添加元素

二分搜索树 - 图4
上图想必已经将添加元素的规则说的很详细了,所以这里直接上代码

  1. public void add(E e) {
  2. if (root == null) {
  3. root = new Node(e);
  4. size++;
  5. } else {
  6. add(root, e);
  7. }
  8. }
  9. private void add(Node node, E e) {
  10. //递归终止条件
  11. if (e.equals(node.e)) {
  12. return;
  13. } else if (e.compareTo(node.e) < 0 && node.left == null) {
  14. node.left = new Node(e);
  15. size++;
  16. return;
  17. } else if (e.compareTo(node.e) > 0 && node.right == null) {
  18. node.right = new Node(e);
  19. size++;
  20. return;
  21. }
  22. if (e.compareTo(node.e) < 0) {
  23. add(node.left, e);
  24. }
  25. if (e.compareTo(node.e) > 0) {
  26. add(node.right, e);
  27. }
  28. }

其实上面的代码还可以改进,因为我们在add(E e)中对root为根节点进行了单独的考虑,其实可以不再这里考虑,因为通过上面的规则知道,当一个节点为null时,不管它是根节点还是左右孩子,新加入的节点都将取代这个null节点
二分搜索树 - 图5
所以我们优化上面的代码如下

  1. public void add(E e) {
  2. root = add(root, e);
  3. }
  4. private Node add(Node node, E e) {
  5. //这里返回的Node所指的语义是node所代表的根节点
  6. if (node == null) {
  7. size++;
  8. return new Node(e);
  9. }
  10. if (e.compareTo(node.e) < 0) {
  11. //如果比根节点小,对左子树进行更新
  12. node.left = add(node.left, e);
  13. } else if (e.compareTo(node.e) > 0) {
  14. //如果比根节点大,对右子树进行更新
  15. node.right = add(node.right,e);
  16. }
  17. //等于的话什么都不做
  18. return node;
  19. }

查询操作

  1. public boolean contains(E e) {
  2. return contains(root, e);
  3. }
  4. private boolean contains(Node node, E e) {
  5. if (node == null) {
  6. return false;
  7. }
  8. if (e.equals(node.e)) {
  9. return true;
  10. } else if (e.compareTo(node.e) < 0) {
  11. return contains(node.left, e);
  12. } else {
  13. return contains(node.right,e);
  14. }
  15. }

二叉树的遍历

对于某个数据结构的遍历就是将该数据结构中所有的元素都访问一遍,分为三类

  • 前序遍历
    • 父节点在访问左子树之前访问
  • 中序遍历
    • 父节点在访问左子树之后,在访问右子树之前访问
  • 后序遍历
    • 父节点在访问右子树之后访问

二分搜索树 - 图6

前序遍历
  1. public void preOrder() {
  2. preOrder(root);
  3. }
  4. private void preOrder(Node node) {
  5. if (node == null) {
  6. return;
  7. }
  8. //先访问父节点
  9. System.out.println(node);
  10. preOrder(node.left);
  11. preOrder(node.right);
  12. }

中序遍历
  1. public void inOrder() {
  2. inOrder(root);
  3. }
  4. private void inOrder(Node node) {
  5. if (node == null) {
  6. return;
  7. }
  8. inOrder(node.left);
  9. System.out.println(node);
  10. inOrder(node.right);
  11. }

中序遍历的结果是元素从小到大排序。

后序遍历
  1. public void postOrder() {
  2. postOrder(root);
  3. }
  4. private void postOrder(Node node) {
  5. if (node == null) {
  6. return;
  7. }
  8. postOrder(node.left);
  9. postOrder(node.right);
  10. System.out.println(node);
  11. }

后序遍历的一个应用是内存释放,我们必须先把左右孩子的内存释放完才能释放该节点的内存。

前序遍历的非递归实现

二分搜索树 - 图7
代码实现

  1. public void preOrderNR() {
  2. //非递归写法
  3. if (root == null) {
  4. return;
  5. }
  6. //这里的Stack是上面自己写的Stack
  7. Stack<Node> stack = new ArrayStack<>();
  8. stack.push(root);
  9. while (!stack.isEmpty()) {
  10. Node top = stack.pop();
  11. System.out.println(top);
  12. if (top.right != null) {
  13. stack.push(top.right);
  14. }
  15. if (top.left != null) {
  16. stack.push(top.left);
  17. }
  18. }
  19. }

层序遍历

二分搜索树 - 图8
代码实现

  1. public void levelOrder() {
  2. if (root == null) {
  3. return;
  4. }
  5. Queue<Node> queue = new LoopQueue<>();
  6. queue.enqueue(root);
  7. while (!queue.isEmpty()) {
  8. Node front = queue.dequeue();
  9. System.out.println(front);
  10. if (front.left != null) {
  11. queue.enqueue(front.left);
  12. }
  13. if (front.right != null) {
  14. queue.enqueue(front.right);
  15. }
  16. }
  17. }

删除元素

在二分搜索树中删除一个节点是比较复杂的,我们首先从最简单的情况开始,删除二分搜索树中的最小值和最大值,首先是如何找到最大值和最小值
二分搜索树 - 图9

  1. public E minimum() {
  2. if (size == 0) {
  3. throw new IllegalArgumentException("树为空");
  4. }
  5. return minimum(root).e;
  6. }
  7. private Node minimum(Node node) {
  8. if (node.left == null) {
  9. return node;
  10. }
  11. return minimum(node.left);
  12. }
  13. public E maximum() {
  14. if (size == 0) {
  15. throw new IllegalArgumentException("树为空");
  16. }
  17. return maximum(root).e;
  18. }
  19. private Node maximum(Node node) {
  20. if (node.right == null) {
  21. return node;
  22. }
  23. return maximum(node.right);
  24. }

找到了之后如何删除呢
二分搜索树 - 图10

  1. public E removeMin() {
  2. E ret = minimum();
  3. root = removeMin(root);
  4. return ret;
  5. }
  6. private Node removeMin(Node node) {
  7. if (node.left == null) {
  8. Node rightNode = node.right;
  9. node.right = null;
  10. size--;
  11. return rightNode;
  12. }
  13. node.left = removeMin(node.left);
  14. return node;
  15. }
  1. public E removeMax() {
  2. E ret = maximum();
  3. root = removeMax(root);
  4. return ret;
  5. }
  6. private Node removeMax(Node node) {
  7. if (node.right == null) {
  8. Node leftNode = node.left;
  9. node.left = null;
  10. size--;
  11. return leftNode;
  12. }
  13. node.right = removeMax(node.right);
  14. return node;
  15. }

现在讲解如何删除二分搜搜数中的任意一个节点
二分搜索树 - 图11

  1. public void remove(E e) {
  2. root = remove(root, e);
  3. }
  4. private Node remove(Node node, E e) {
  5. if (node == null) {
  6. return null;
  7. }
  8. if (e.equals(node.e)) {
  9. if (node.right == null) {
  10. Node leftNode = node.left;
  11. node.left = null;
  12. size--;
  13. return leftNode;
  14. } else if (node.left == null) {
  15. Node rightNode = node.right;
  16. node.right = null;
  17. size--;
  18. return rightNode;
  19. } else {
  20. Node successor = minimum(node.right);
  21. successor.right = removeMin(node.right);//为什么这条语句必须在前面???
  22. successor.left = node.left;
  23. node.left = node.right = null;
  24. //size--; 在removeMin中已经维护size了
  25. return successor;
  26. }
  27. } else if (e.compareTo(node.e) < 0) {
  28. node.left = remove(node.left, e);
  29. } else {
  30. node.right = remove(node.right, e);
  31. }
  32. return node;
  33. }

上面说的是取右子树中的最小值,你也可以考虑取左子树中的最大值,道理都是一样的。

完整代码

  1. public class BST<E extends Comparable<E>> {
  2. private class Node {
  3. public E e;
  4. public Node left;
  5. public Node right;
  6. public Node(E e) {
  7. this.e = e;
  8. left = null;
  9. right = null;
  10. }
  11. @Override
  12. public String toString() {
  13. return e.toString();
  14. }
  15. }
  16. //根节点
  17. private Node root;
  18. //树中元素的个数
  19. private int size;
  20. public BST() {
  21. root = null;
  22. size = 0;
  23. }
  24. public int size() {
  25. return size;
  26. }
  27. public boolean isEmpty() {
  28. return size == 0;
  29. }
  30. public void add(E e) {
  31. root = add(root, e);
  32. }
  33. private Node add(Node node, E e) {
  34. //这里返回语义指的的是node所代表的根节点
  35. if (node == null) {
  36. size++;
  37. return new Node(e);
  38. }
  39. if (e.compareTo(node.e) < 0) {
  40. //如果比根节点小,对左子树进行更新
  41. node.left = add(node.left, e);
  42. } else if (e.compareTo(node.e) > 0) {
  43. //如果比根节点大,对右子树进行更新
  44. node.right = add(node.right,e);
  45. }
  46. //等于的话什么都不做
  47. return node;
  48. }
  49. public boolean contains(E e) {
  50. return contains(root, e);
  51. }
  52. private boolean contains(Node node, E e) {
  53. if (node == null) {
  54. return false;
  55. }
  56. if (e.equals(node.e)) {
  57. return true;
  58. } else if (e.compareTo(node.e) < 0) {
  59. return contains(node.left, e);
  60. } else {
  61. return contains(node.right,e);
  62. }
  63. }
  64. //前序遍历
  65. public void preOrder() {
  66. preOrder(root);
  67. }
  68. private void preOrder(Node node) {
  69. if (node == null) {
  70. return;
  71. }
  72. System.out.println(node);
  73. preOrder(node.left);
  74. preOrder(node.right);
  75. }
  76. public void preOrderNR() {
  77. //非递归写法
  78. if (root == null) {
  79. return;
  80. }
  81. Stack<Node> stack = new ArrayStack<>();
  82. stack.push(root);
  83. while (!stack.isEmpty()) {
  84. Node top = stack.pop();
  85. System.out.println(top);
  86. if (top.right != null) {
  87. stack.push(top.right);
  88. }
  89. if (top.left != null) {
  90. stack.push(top.left);
  91. }
  92. }
  93. }
  94. //中序遍历
  95. public void inOrder() {
  96. inOrder(root);
  97. }
  98. private void inOrder(Node node) {
  99. if (node == null) {
  100. return;
  101. }
  102. inOrder(node.left);
  103. System.out.println(node);
  104. inOrder(node.right);
  105. }
  106. //后序遍历
  107. public void postOrder() {
  108. postOrder(root);
  109. }
  110. private void postOrder(Node node) {
  111. if (node == null) {
  112. return;
  113. }
  114. postOrder(node.left);
  115. postOrder(node.right);
  116. System.out.println(node);
  117. }
  118. public void levelOrder() {
  119. if (root == null) {
  120. return;
  121. }
  122. Queue<Node> queue = new LoopQueue<>();
  123. queue.enqueue(root);
  124. while (!queue.isEmpty()) {
  125. Node front = queue.dequeue();
  126. System.out.println(front);
  127. if (front.left != null) {
  128. queue.enqueue(front.left);
  129. }
  130. if (front.right != null) {
  131. queue.enqueue(front.right);
  132. }
  133. }
  134. }
  135. public E minimum() {
  136. if (size == 0) {
  137. throw new IllegalArgumentException("树为空");
  138. }
  139. return minimum(root).e;
  140. }
  141. private Node minimum(Node node) {
  142. if (node.left == null) {
  143. return node;
  144. }
  145. return minimum(node.left);
  146. }
  147. public E maximum() {
  148. if (size == 0) {
  149. throw new IllegalArgumentException("树为空");
  150. }
  151. return maximum(root).e;
  152. }
  153. private Node maximum(Node node) {
  154. if (node.right == null) {
  155. return node;
  156. }
  157. return maximum(node.right);
  158. }
  159. public E removeMin() {
  160. E ret = minimum();
  161. root = removeMin(root);
  162. return ret;
  163. }
  164. private Node removeMin(Node node) {
  165. if (node.left == null) {
  166. Node rightNode = node.right;
  167. node.right = null;
  168. size--;
  169. return rightNode;
  170. }
  171. node.left = removeMin(node.left);
  172. return node;
  173. }
  174. public E removeMax() {
  175. E ret = maximum();
  176. root = removeMax(root);
  177. return ret;
  178. }
  179. private Node removeMax(Node node) {
  180. if (node.right == null) {
  181. Node leftNode = node.left;
  182. node.left = null;
  183. size--;
  184. return leftNode;
  185. }
  186. node.right = removeMax(node.right);
  187. return node;
  188. }
  189. public void remove(E e) {
  190. root = remove(root, e);
  191. }
  192. private Node remove(Node node, E e) {
  193. if (node == null) {
  194. return null;
  195. }
  196. if (e.equals(node.e)) {
  197. if (node.right == null) {
  198. Node leftNode = node.left;
  199. node.left = null;
  200. size--;
  201. return leftNode;
  202. } else if (node.left == null) {
  203. Node rightNode = node.right;
  204. node.right = null;
  205. size--;
  206. return rightNode;
  207. } else {
  208. Node successor = minimum(node.right);
  209. successor.right = removeMin(node.right);//为什么这条语句必须在前面???
  210. successor.left = node.left;
  211. node.left = node.right = null;
  212. //size--; 在removeMin中已经维护size了
  213. return successor;
  214. }
  215. } else if (e.compareTo(node.e) < 0) {
  216. node.left = remove(node.left, e);
  217. } else {
  218. node.right = remove(node.right, e);
  219. }
  220. return node;
  221. }
  222. }