• 二叉排序树相关概念以及性质
  • 在二叉搜索树中添加元素: add()
  • 查看二叉搜索树中是否存在某个元素 : contains()
  • 找出二叉搜索树中的最小值和最小值 :minimum()maximum()
  • 删除二叉搜索树中的最小结点和最大结点: removeMin()removeMax()
  • 删除二叉搜索树中的任意结点: remove()
  • 完整测试源代码

一、 二叉排序树相关概念以及性质

二叉查找树(Binary Search Tree),也称二叉搜索树,是指一棵空树或者具有下列性质的二叉树:

  • 任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  • 任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值
  • 任意节点的左、右子树也分别为二叉查找树;
  • 没有键值相等的节点(一般)。

相关的性质:

  • 二叉查找树相比于其他数据结构的优势在于查找、插入的时间复杂度较低,为O(log n)
  • 二叉排序树要求所有的项都能排序,要写出一个一般的类,我们需要实现Comparable接口,使得可以使用泛型来比较任意的数据类型。注意这里不是使用equals方法来实现,根据两项相等当且仅当compareTo方法返回0
    这里给出BSTreeNode 的定义结构类
  1. public class BSTree<E extends Comparable<E>> {
  2. private class Node{
  3. public E e;
  4. public Node left,right;
  5. public Node(E e, Node left, Node right) {
  6. this.e = e;
  7. this.left = left;
  8. this.right = right;
  9. }
  10. public Node(E e) {
  11. this.e = e;
  12. left = null;
  13. right = null;
  14. }
  15. }
  16. private Node root;
  17. private int size;
  18. public BSTree() {
  19. root = null;
  20. size = 0;
  21. }
  22. public int size(){
  23. return size;
  24. }
  25. public boolean isEmpty(){
  26. return size == 0;
  27. }
  28. }

二、在二叉搜索树中添加元素: add()

  • 如果根节点为null,就新建一个根节点;
  • 如果要插入的元素比当前访问结点node小,而且node.left == null,就可以插入到node的左孩子那里
  • 如果要插入的元素比当前访问结点node大,而且node.right == null,就可以插入到node的右孩子那里
  • 如果要插入的元素比当前访问结点node小,而且node.left != null,就递归的去左子树插入这个值
  • 如果要插入的元素比当前访问结点node大,而且node.right != null,就递归的去右子树插入这个值

二叉排序树相关总结 - 图1

  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. // 向以node为根的二分搜索树中插入元素,递归算法
  10. private void add(Node node, E e) {
  11. if(node.e.equals(e))return;
  12. if(e.compareTo(node.e) < 0 && node.left == null){ //比node.e小
  13. node.left = new Node(e);
  14. size++;
  15. return;
  16. }else if(e.compareTo(node.e) > 0 && node.right == null){
  17. node.right = new Node(e);
  18. size++;
  19. return;
  20. }
  21. if(e.compareTo(node.e) < 0)// e < node.e && node.left != null
  22. add(node.left,e);
  23. else //e > node.e && node.right != null
  24. add(node.right,e);
  25. }
  • 更加简单的写法,也就是当我们递归插入的时候,再多递归一层,当递归到空结点的时候,就插入这个结点,注意add(Node node,E e)方法的作用:
  • 向以node为根的二分搜索树中插入元素,返回插入新结点之后二分搜索树的根,我们在递归的时候要得到返回值和上一层的结点进行连接:
  1. public void add(E e){
  2. root = add(root,e);
  3. }
  4. private Node add(Node node, E e) { /**向以node为根的二分搜索树中插入元素,返回插入新结点之后二分搜索树的根 */
  5. if(node == null) {//在这里插入
  6. size++;
  7. return new Node(e);
  8. }
  9. if(e.compareTo(node.e) < 0){
  10. node.left = add(node.left,e); //node.left是变化的
  11. }else if(e.compareTo(node.e) > 0){
  12. node.right = add(node.right,e);
  13. }
  14. return node;
  15. }

三、查看二叉搜索树中是否存在某个元素 : contains()

在二叉搜索树bsTree中查找x的过程为:

  • bsTree是空树,则返回false
  • x等于bsTree的根节点的数据域之值,则查找成功;
  • x小于bsTree的根节点的数据域之值,则搜索左子树;
  • 否则搜索右子树;

二叉排序树相关总结 - 图2
代码如下:

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

四、找出二叉搜索树中的最小值和最小值 : minimum()maximum()

实现很简单就是一直往左边,或者一直忘右边走,递归和非递归实现都很简单;

  1. //min
  2. public E minimum(){
  3. if(size == 0)
  4. throw new IllegalArgumentException("BST is empty!");
  5. return minimum(root).e;
  6. }
  7. private Node minimum(Node node) {
  8. if(node.left == null)return node;
  9. return minimum(node.left);
  10. }
  11. //max
  12. public E maximum(){
  13. if(size == 0)
  14. throw new IllegalArgumentException("BST is empty!");
  15. return maximum(root).e;
  16. }
  17. private Node maximum(Node node){
  18. if(node.right == null)return node;
  19. return maximum(node.right);
  20. }

五、删除二叉搜索树中的最小结点和最大结点: removeMin()removeMax()

先看删除最小结点:

  • 如果最小结点是叶子结点,很简单直接删除即可;

  • 如果不是叶子结点,就删除这个结点,并且将它的父亲和它的右孩子连接起来即可,看下图:

二叉排序树相关总结 - 图3

删除之后就是下面的样子:

二叉排序树相关总结 - 图4

注意这里的连接就是按照返回值的形式,父亲的左孩子连接上删除之后的树的根即可。

  1. // 删除二叉搜索树中最小值所在的结点,返回最小值
  2. public E removeMin(){
  3. E ret = minimum();
  4. root = removeMin(root); //remove the min node and then connect the tree
  5. return ret;
  6. }
  7. private Node removeMin(Node node){/**删除掉以node为根的二叉搜索树中的最小结点,返回删除节点后新的结点的二分搜索树的根 */
  8. if(node.left == null){ //node is min
  9. Node rightNode = node.right;
  10. node.right = null; //remove from the tree
  11. size--;
  12. return rightNode; /** 返回删除节点后新的结点的二分搜索树的根 然后上一层就可以连接上*/
  13. }
  14. node.left = removeMin(node.left); /** 去删除左子树, 然后我要连上你删除之后返回的新的根*/
  15. return node; /** 返回删除之后的根 ,上一层还要连接*/
  16. }

删除最大结点同理:

  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. }

六、删除二叉搜索树中的任意结点: remove()

删除结点比较复杂,分为四种情况,前三种情况很简单,最后一种情况稍微复杂一点:

  • 如果是叶子结点,直接删除;
  • 如果node只有右孩子,和之前removeMin()的处理一样,删除之后,返回右子树的根节点;
  • 如果node只有左孩子,和之前removeMax()的处理一样,删除之后,返回左子树的根节点;
  • 如果node既有左孩子又有右孩子,就找到node的右子树最小的结点 (后继结点)successor结点,然后用这个结点来顶替node,具体过程和实现看图片和代码

二叉排序树相关总结 - 图5

二叉排序树相关总结 - 图6
二叉排序树相关总结 - 图7
二叉排序树相关总结 - 图8

代码:

  1. //remove any node
  2. public void remove(E e){
  3. root = remove(root,e);
  4. }
  5. private Node remove(Node node,E e){/**删除以node为根的二分搜索树中值为e的节点,返回删除节点后新的二叉搜索树的根*/
  6. if(node == null) return null; // 树中没有这个结点
  7. if(e.compareTo(node.e) < 0){
  8. node.left = remove(node.left,e);
  9. return node;
  10. }else if(e.compareTo(node.e) > 0){
  11. node.right = remove(node.right,e);
  12. return node;
  13. }else { //e == node.e ---> should remove
  14. if(node.left == null){ // only have rightchild or leaf
  15. Node rightNode = node.right;
  16. node.right = null;
  17. size--;
  18. return rightNode;
  19. }
  20. if(node.right == null){
  21. Node leftNode = node.left;
  22. node.left = null;
  23. size--;
  24. return leftNode;
  25. }
  26. /** 左右子树都不为空 : 找到比待删除结点大的最小结点(后继), 用这个结点顶替待删除结点的位置*/
  27. Node successor = minimum(node.right); //找到右子树的最小结点
  28. successor.right = removeMin(node.right); //将successor.right设置成 原先结点node的右子树移除 successor之后的树
  29. successor.left = node.left; //
  30. //remove node
  31. node.left = node.right = null;
  32. //size--; //这个不需要,因为在removeMin(node.right)中已经减了一次
  33. return successor; //返回新树的根
  34. }
  35. }

还有一个要注意的就是删除之后不需要size--,因为我们在node的右子树中删除那个最小的(successor)结点并顶替node的时候,在removeMin()中已经size- -了,所以不要再减去。


七、完整测试代码

  1. package DataStructure.Tree.BST;
  2. public class BSTree<E extends Comparable<E>> {
  3. private class Node{
  4. public E e;
  5. public Node left,right;
  6. public Node(E e, Node left, Node right) {
  7. this.e = e;
  8. this.left = left;
  9. this.right = right;
  10. }
  11. public Node(E e) {
  12. this.e = e;
  13. left = null;
  14. right = null;
  15. }
  16. }
  17. private Node root;
  18. private int size;
  19. public BSTree() {
  20. root = null;
  21. size = 0;
  22. }
  23. public int size(){
  24. return size;
  25. }
  26. public boolean isEmpty(){
  27. return size == 0;
  28. }
  29. /**
  30. //easy understand version
  31. public void add(E e){
  32. if(root == null){
  33. root = new Node(e);
  34. size++;
  35. }else {
  36. add(root,e);
  37. }
  38. }
  39. // 向以node为根的二分搜索树中插入元素,递归算法
  40. private void add(Node node, E e) {
  41. if(node.e.equals(e))return;
  42. if(e.compareTo(node.e) < 0 && node.left == null){ //比node.e小
  43. node.left = new Node(e);
  44. size++;
  45. return;
  46. }else if(e.compareTo(node.e) > 0 && node.right == null){
  47. node.right = new Node(e);
  48. size++;
  49. return;
  50. }
  51. if(e.compareTo(node.e) < 0)// e < node.e && node.left != null
  52. add(node.left,e);
  53. else //e > node.e && node.right != null
  54. add(node.right,e);
  55. }
  56. */
  57. //add
  58. public void add(E e){
  59. root = add(root,e);
  60. }
  61. private Node add(Node node, E e) { /**向以node为根的二分搜索树中插入元素,返回插入新结点之后二分搜索树的根 */
  62. if(node == null) {//在这里插入
  63. size++;
  64. return new Node(e);
  65. }
  66. if(e.compareTo(node.e) < 0){
  67. node.left = add(node.left,e); //node.left是变化的
  68. }else if(e.compareTo(node.e) > 0){
  69. node.right = add(node.right,e);
  70. }
  71. return node;
  72. }
  73. //query
  74. public boolean contains(E e){
  75. return contains(root,e);
  76. }
  77. private boolean contains(Node node, E e) {
  78. if (node == null) return false;
  79. if (e.compareTo(node.e) == 0) {
  80. return true;
  81. } else if (e.compareTo(node.e) < 0) {
  82. return contains(node.left, e);
  83. } else {
  84. return contains(node.right, e);
  85. }
  86. }
  87. public E minimum(){
  88. if(size == 0)
  89. throw new IllegalArgumentException("BST is empty!");
  90. return minimum(root).e;
  91. }
  92. private Node minimum(Node node) {
  93. if(node.left == null)return node;
  94. return minimum(node.left);
  95. }
  96. public E maximum(){
  97. if(size == 0)
  98. throw new IllegalArgumentException("BST is empty!");
  99. return maximum(root).e;
  100. }
  101. private Node maximum(Node node){
  102. if(node.right == null)return node;
  103. return maximum(node.right);
  104. }
  105. // 删除二叉搜索树中最小值所在的结点,返回最小值
  106. public E removeMin(){
  107. E ret = minimum();
  108. root = removeMin(root); //remove the min node and then connect the tree
  109. return ret;
  110. }
  111. private Node removeMin(Node node){/**删除掉以node为根的二叉搜索树中的最小结点,返回删除节点后新的结点的二分搜索树的根 */
  112. if(node.left == null){ //node is min
  113. Node rightNode = node.right;
  114. node.right = null; //remove from the tree
  115. size--;
  116. return rightNode; /** 返回删除节点后新的结点的二分搜索树的根 然后上一层就可以连接上*/
  117. }
  118. node.left = removeMin(node.left); /** 去删除左子树, 然后我要连上你删除之后返回的新的根*/
  119. return node; /** 返回删除之后的根 ,上一层还要连接*/
  120. }
  121. public E removeMax(){
  122. E ret = maximum();
  123. root = removeMax(root);
  124. return ret;
  125. }
  126. private Node removeMax(Node node){
  127. if(node.right == null){
  128. Node leftNode = node.left;
  129. node.left = null;
  130. size--;
  131. return leftNode;
  132. }
  133. node.right = removeMax(node.right);
  134. return node;
  135. }
  136. //remove any node
  137. public void remove(E e){
  138. root = remove(root,e);
  139. }
  140. private Node remove(Node node,E e){/**删除以node为根的二分搜索树中值为e的节点,返回删除节点后新的二叉搜索树的根*/
  141. if(node == null) return null; // 树中没有这个结点
  142. if(e.compareTo(node.e) < 0){
  143. node.left = remove(node.left,e);
  144. return node;
  145. }else if(e.compareTo(node.e) > 0){
  146. node.right = remove(node.right,e);
  147. return node;
  148. }else { //e == node.e ---> should remove
  149. if(node.left == null){ // only have rightchild or leaf
  150. Node rightNode = node.right;
  151. node.right = null;
  152. size--;
  153. return rightNode;
  154. }
  155. if(node.right == null){
  156. Node leftNode = node.left;
  157. node.left = null;
  158. size--;
  159. return leftNode;
  160. }
  161. /** 左右子树都不为空 : 找到比待删除结点大的最小结点(后继), 用这个结点顶替待删除结点的位置*/
  162. Node successor = minimum(node.right); //找到右子树的最小结点
  163. successor.right = removeMin(node.right); //将successor.right设置成 原先结点node的右子树移除 successor之后的树
  164. successor.left = node.left; //
  165. //remove node
  166. node.left = node.right = null;
  167. //size--; //这个不需要,因为在removeMin(node.right)中已经减了一次
  168. return successor; //返回新树的根
  169. }
  170. }
  171. public void printTree(){
  172. printTree(root,0,"H",8);
  173. }
  174. public void printTree(Node head,int height,String to,int len){
  175. if(head == null)return;
  176. printTree(head.right,height + 1,"v",len);
  177. String val = to + head.e + to; //两边指示的字符
  178. int lenV = val.length();
  179. int lenL = (len - lenV)/2; //左边的空格(分一半)
  180. int lenR = len - lenV - lenL; // 右边的空格
  181. System.out.println( getSpace(len * height) + getSpace(lenL) + val + getSpace(lenR));
  182. printTree(head.left,height + 1,"^",len);
  183. }
  184. public static String getSpace(int len){
  185. StringBuffer str = new StringBuffer();
  186. for(int i = 0; i < len; i++) str.append(" ");
  187. return str.toString();
  188. }
  189. public static void main(String[] args) {
  190. Integer[] arr = {21,14,28,11,18,25,32,5,12,15,19,23,27,30,37};
  191. // Arrays.sort(arr); //退化成链表
  192. BSTree<Integer>bsTree = new BSTree<>();
  193. for(int i = 0; i < arr.length; i++) bsTree.add(arr[i]);
  194. bsTree.printTree();
  195. System.out.println("--------------华丽分割线-------------");
  196. System.out.println(bsTree.contains(27));
  197. System.out.println(bsTree.contains(99));
  198. System.out.println(bsTree.minimum());
  199. System.out.println(bsTree.maximum());
  200. System.out.println("--------------华丽分割线-------------");
  201. // bsTree.removeMin();
  202. // bsTree.removeMax();
  203. // bsTree.printTree();
  204. bsTree.remove(25);
  205. bsTree.printTree();
  206. }
  207. }

效果:

二叉排序树相关总结 - 图9

  • 打印二叉树见这个博客
  • 注意到我在main中测试时注释了Arrays.sort(arr);这一行,因为如果是这样插入的话就会退化成链表,这个进阶的问题就是平衡二叉树