当前位置:   article > 正文

【数据结构】用Java实现二叉搜索树(二分搜索树)_java 二叉搜索树

java 二叉搜索树

1. 概念

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别为二叉搜索树
  • 一般来说二叉搜索树不存在重复的元素(JDK中的二叉搜索树也不存在重复的元素)

2. 具体实现

2.1 MyBST类

  1. public class MyBST {
  2. private TreeNode root;
  3. private int size;
  4. static class TreeNode {
  5. int val;
  6. TreeNode left;//左孩子的引用
  7. TreeNode right;//右孩子的引用
  8. public TreeNode(int val) {
  9. this.val = val;
  10. }
  11. }
  12. }

2.2 插入

(1)如果树为空树,即根 == null,直接插入

(2)如果树不是空树,按照查找逻辑确定插入位置,插入新结点(val < root.val,在root的左子树中插入;val > root.val,在root的右子树中插入)(默认不存在重复的元素)

  1. public void add(int val){
  2. root = add(root,val);
  3. }
  4. // 插入一个新的值val,
  5. private TreeNode add(TreeNode root, int val) {
  6. if (root == null){
  7. TreeNode node = new TreeNode(val);
  8. size++;
  9. return node;
  10. } else if (val < root.val) {
  11. root.left = add(root.left,val);
  12. return root;
  13. }
  14. root.right = add(root.right,val);
  15. return root;
  16. }

2.3 查找树的最大值

树为空,返回null

树的最大值在数的最右边

  1. // 找最大,树的最右节点
  2. public int Max(){
  3. if (root == null){
  4. throw new NoSuchElementException("bst is empty! Do no has max node");
  5. }
  6. TreeNode node = findMax(root);
  7. return node.val;
  8. }
  9. private TreeNode findMax(TreeNode root){
  10. if(root.right == null){
  11. return root;
  12. }
  13. return findMax(root.right);
  14. }

2.4 查找树的最小值

树为空,返回null

树的最小值在数的最左边

  1. // 找最小,树的最左节点
  2. public int Min(){
  3. if (root == null){
  4. throw new NoSuchElementException("bst is empty! Do no has max node");
  5. }
  6. TreeNode node = findMin(root);
  7. return node.val;
  8. }
  9. private TreeNode findMin(TreeNode root){
  10. if(root.left == null){
  11. return root;
  12. }
  13. return findMin(root.left);
  14. }

2.5 查找任意值

树为空,返回false

val == root.val 找到了,返回true;

val < root.val,在root的左子树找;

val > root.val,在root的右子树找

  1. // 查找任意值
  2. public boolean contains(int val){
  3. return contains(root,val);
  4. }
  5. private boolean contains(TreeNode root, int val) {
  6. if(root == null){
  7. return false;
  8. }
  9. if(root.val == val){
  10. return true;
  11. } else if (root.val > val) {
  12. return contains(root.left,val);
  13. }
  14. return contains(root.right,val);
  15. }

2.6 删除最大值

找到树的最大值,返回它的左子树节点

  1. // 删除最大值
  2. public int removeMax(){
  3. TreeNode node = findMax(root);
  4. root = removeMax(root);
  5. return node.val;
  6. }
  7. private TreeNode removeMax(TreeNode root) {
  8. if(root.right == null) {
  9. TreeNode left = root.left;
  10. root = root.left = null;
  11. size--;
  12. return left;
  13. }
  14. root.right = removeMax(root.right);
  15. return root;
  16. }

2.7 删除最小值

找到树的最小值,返回它的右子树节点

  1. // 删除最小值
  2. public int removeMin(){
  3. TreeNode node = findMin(root);
  4. root = removeMin(root);
  5. return node.val;
  6. }
  7. private TreeNode removeMin(TreeNode root) {
  8. if(root.left == null){
  9. TreeNode right = root.right;
  10. root.right = root = null;
  11. size--;
  12. return right;
  13. }
  14. root.left = removeMin(root.left);
  15. return root;
  16. }

2.8 删除任意值

(1)找到需要删除的节点
(2)需要删除的节点的左子树为空,返回右子树(可能也为空);需要删除的节点的右子树为空,返回左子树;需要删除的节点的左右子树都不为空:需要使用替换法进行删除,即在它的右子树中找最小值,用它的值填补到被删除节点中,再来处理该结点的删除问题(或者在左子树中找最大值)
需要删除的节点的右子树中的最小值:需要删除的节点的左子树的每一个值都比它小
  1. // 删除任意值 Hibbard Deletion 1962
  2. public void removeValue(int val){
  3. remove(root,val);
  4. }
  5. private TreeNode remove(TreeNode root, int val) {
  6. if(root == null){
  7. return null;
  8. } else if (val < root.val) {
  9. root.left = remove(root.left,val);
  10. return root;
  11. }else if (val > root.val){
  12. root.right = remove(root.right,val);
  13. return root;
  14. }else {
  15. if(root.left == null){
  16. TreeNode right = root.right;
  17. root.right = root = null;
  18. size--;
  19. return right;
  20. }else if (root.right == null){
  21. TreeNode left = root.left;
  22. root.left = root = null;
  23. size--;
  24. return left;
  25. }
  26. TreeNode suor = findMin(root.right);
  27. // 移除右子树的最小值时维护了size属性~
  28. // 一定要先删除再连左子树
  29. suor.right = removeMin(root.right);
  30. suor.left = root.left;
  31. root.left = root.right = root = null;
  32. return suor;
  33. }
  34. }

2.9 普通中序打印输出

  1. public void print(){
  2. print(root);
  3. }
  4. private void print(TreeNode root) {
  5. if (root == null){
  6. return;
  7. }
  8. print(root.left);
  9. System.out.print(root.val + " ");
  10. print(root.right);
  11. }

2.10 美观的中序打印输出

  1. // toString打印
  2. @Override
  3. public String toString() {
  4. StringBuilder sb = new StringBuilder();
  5. generateBSTString(root,0,sb);
  6. return sb.toString();
  7. }
  8. private void generateBSTString(TreeNode root, int depth, StringBuilder sb) {
  9. if(root == null){
  10. sb.append(generateDepthString(depth) + "null\n");
  11. return;
  12. }
  13. sb.append(generateDepthString(depth) + root.val + "\n");
  14. generateBSTString(root.left,depth + 1,sb);
  15. generateBSTString(root.right,depth + 1,sb);
  16. }
  17. // 打印--
  18. // 层数越深,--越多
  19. private String generateDepthString(int depth) {
  20. StringBuilder res = new StringBuilder();
  21. for (int i=0; i<depth; i++){
  22. res.append("--");
  23. }
  24. return res.toString();
  25. }

3. 整体代码

  1. import java.util.NoSuchElementException;
  2. /**
  3. * 二分搜索树,一般不存相同值的元素
  4. */
  5. public class MyBST {
  6. private TreeNode root;
  7. private int size;
  8. static class TreeNode {
  9. int val;
  10. TreeNode left;//左孩子的引用
  11. TreeNode right;//右孩子的引用
  12. public TreeNode(int val) {
  13. this.val = val;
  14. }
  15. }
  16. public void add(int val){
  17. root = add(root,val);
  18. }
  19. // 插入一个新的值val,
  20. private TreeNode add(TreeNode root, int val) {
  21. if (root == null){
  22. TreeNode node = new TreeNode(val);
  23. size++;
  24. return node;
  25. } else if (val < root.val) {
  26. root.left = add(root.left,val);
  27. return root;
  28. }
  29. root.right = add(root.right,val);
  30. return root;
  31. }
  32. // 找最大,树的最右节点
  33. public int Max(){
  34. if (root == null){
  35. throw new NoSuchElementException("bst is empty! Do no has max node");
  36. }
  37. TreeNode node = findMax(root);
  38. return node.val;
  39. }
  40. private TreeNode findMax(TreeNode root){
  41. if(root.right == null){
  42. return root;
  43. }
  44. return findMax(root.right);
  45. }
  46. // 删除最大值
  47. public int removeMax(){
  48. TreeNode node = findMax(root);
  49. root = removeMax(root);
  50. return node.val;
  51. }
  52. private TreeNode removeMax(TreeNode root) {
  53. if(root.right == null) {
  54. TreeNode left = root.left;
  55. root = root.left = null;
  56. size--;
  57. return left;
  58. }
  59. root.right = removeMax(root.right);
  60. return root;
  61. }
  62. // 找最小,树的最左节点
  63. public int Min(){
  64. if (root == null){
  65. throw new NoSuchElementException("bst is empty! Do no has max node");
  66. }
  67. TreeNode node = findMin(root);
  68. return node.val;
  69. }
  70. private TreeNode findMin(TreeNode root){
  71. if(root.left == null){
  72. return root;
  73. }
  74. return findMin(root.left);
  75. }
  76. // 删除最小值
  77. public int removeMin(){
  78. TreeNode node = findMin(root);
  79. root = removeMin(root);
  80. return node.val;
  81. }
  82. private TreeNode removeMin(TreeNode root) {
  83. if(root.left == null){
  84. TreeNode right = root.right;
  85. root.right = root = null;
  86. size--;
  87. return right;
  88. }
  89. root.left = removeMin(root.left);
  90. return root;
  91. }
  92. // 查找任意值
  93. public boolean contains(int val){
  94. return contains(root,val);
  95. }
  96. private boolean contains(TreeNode root, int val) {
  97. if(root == null){
  98. return false;
  99. }
  100. if(root.val == val){
  101. return true;
  102. } else if (root.val > val) {
  103. return contains(root.left,val);
  104. }
  105. return contains(root.right,val);
  106. }
  107. // 删除任意值 Hibbard Deletion 1962
  108. public void removeValue(int val){
  109. remove(root,val);
  110. }
  111. private TreeNode remove(TreeNode root, int val) {
  112. if(root == null){
  113. return null;
  114. } else if (val < root.val) {
  115. root.left = remove(root.left,val);
  116. return root;
  117. }else if (val > root.val){
  118. root.right = remove(root.right,val);
  119. return root;
  120. }else {
  121. if(root.left == null){
  122. TreeNode right = root.right;
  123. root.right = root = null;
  124. size--;
  125. return right;
  126. }else if (root.right == null){
  127. TreeNode left = root.left;
  128. root.left = root = null;
  129. size--;
  130. return left;
  131. }
  132. TreeNode suor = findMin(root.right);
  133. // 移除右子树的最小值时维护了size属性~
  134. suor.right = removeMin(root.right);
  135. suor.left = root.left;
  136. root.left = root.right = root = null;
  137. return suor;
  138. }
  139. }
  140. // 普通打印
  141. public void print(){
  142. print(root);
  143. }
  144. private void print(TreeNode root) {
  145. if (root == null){
  146. return;
  147. }
  148. print(root.left);
  149. System.out.print(root.val + " ");
  150. print(root.right);
  151. }
  152. // toString打印
  153. @Override
  154. public String toString() {
  155. StringBuilder sb = new StringBuilder();
  156. generateBSTString(root,0,sb);
  157. return sb.toString();
  158. }
  159. private void generateBSTString(TreeNode root, int depth, StringBuilder sb) {
  160. if(root == null){
  161. sb.append(generateDepthString(depth) + "null\n");
  162. return;
  163. }
  164. sb.append(generateDepthString(depth) + root.val + "\n");
  165. generateBSTString(root.left,depth + 1,sb);
  166. generateBSTString(root.right,depth + 1,sb);
  167. }
  168. private String generateDepthString(int depth) {
  169. StringBuilder res = new StringBuilder();
  170. for (int i=0; i<depth; i++){
  171. res.append("--");
  172. }
  173. return res.toString();
  174. }
  175. }

4. 性能分析

4.1 理论分析

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
对有n个结点的二叉搜索树,若每个元素查找的概率相等,则 二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。
但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:

最优情况下,二叉搜索树为完全二叉树,其平均比较次数为:log2N
最差情况下,二叉搜索树退化为单支树,其平均比较次数为:N2
问题:如果退化成单支树,二叉搜索树的性能就失去了。那能否进行改进,不论按照什么次序插入关键码,都可以是二叉搜索树的性能最佳?

答:TreeMap TreeSet java 中利用搜索树实现的 Map Set ;实际上用的是红黑树,而红黑树是一棵近似平衡的二叉搜索树,即在二叉搜索树的基础之上 + 颜色以及红黑树性质验证。关于红黑树的内容后序再进行讲解,现在只简单使用一些。

4.2 代码实测

4.2.1 生成随机数组与近似有序的数组

  1. import java.util.Arrays;
  2. import java.util.concurrent.ThreadLocalRandom;
  3. public class ArrayUtil {
  4. private static ThreadLocalRandom random = ThreadLocalRandom.current();
  5. // 生成一个大小为n的近乎有序的数组
  6. // 数组的有序与否取决于元素的交互次数
  7. public static int[] generateSortedArray(int n,int swapTimes){
  8. int[] arr = new int[n];
  9. for (int i = 0; i < n; i++) {
  10. arr[i] = i + 1;
  11. }
  12. for (int i = 0; i < swapTimes; i++) {
  13. int x = random.nextInt(0,n);
  14. int y = random.nextInt(0,n);
  15. swap(arr,x,y);
  16. }
  17. return arr;
  18. }
  19. private static void swap(int[] arr,int x, int y) {
  20. int temp = arr[x];
  21. arr[x] = arr[y];
  22. arr[y] = temp;
  23. }
  24. // 生成一个大小为n的随机数数组
  25. // 随机数的取值范围为[l..r)
  26. public static int[] generateRandomArray(int n,int l,int r){
  27. int[] arr = new int[n];
  28. for (int i = 0; i < n; i++) {
  29. arr[i] = random.nextInt(l,r);
  30. }
  31. return arr;
  32. }
  33. // 深拷贝数组
  34. public static int[] arrCopy(int[] arr) {
  35. return Arrays.copyOf(arr,arr.length);
  36. }
  37. }

4.2.2 测试代码

测试红黑树只需要将二分搜索树的代码注释掉,换成红黑树的就好(代码中我已经给了哪些是二分搜索树的代码,哪些是红黑树的代码)

  1. import MyBST;
  2. import java.util.LinkedList;
  3. import java.util.TreeMap;
  4. import static util.ArrayUtil.generateRandomArray;
  5. import static util.ArrayUtil.generateSortedArray;
  6. public class BSTPerformanceTest {
  7. public static void main(String[] args) {
  8. LinkedList<Integer> list = new LinkedList<>();
  9. // 红黑树又称红-黑二叉树,红黑树是一颗自平衡的排序二叉树。
  10. // TreeMap<Integer,Integer> bst = new TreeMap<>();
  11. 渐进有序的数组
  12. // int n = 10_0000;
  13. // int[] arr = generateSortedArray(n,50);
  14. // 二分搜索树
  15. MyBST bst = new MyBST();
  16. // 乱序的数组
  17. int n = 100_0000;
  18. int[] arr = generateRandomArray(n,0,Integer.MAX_VALUE);
  19. System.out.println("-----------------------插入-----------------------------");
  20. Long start = System.nanoTime();
  21. for (int i:arr
  22. ) {
  23. list.add(i);
  24. }
  25. Long end = System.nanoTime();
  26. System.out.println("链表插入" + (end - start)/1000000.0 + " ms");
  27. start = System.nanoTime();
  28. for (int i:arr
  29. ) {
  30. // 二叉树搜索树
  31. bst.add(i);
  32. // 红黑树
  33. // bst.put(i,i);
  34. }
  35. end = System.nanoTime();
  36. System.out.println("BST插入" + (end - start)/1000000.0 + " ms");
  37. // System.out.println("红黑树插入" + (end - start)/1000000.0 + " ms");
  38. System.out.println("-----------------------查询-----------------------------");
  39. start = System.nanoTime();
  40. list.contains(999);
  41. end = System.nanoTime();
  42. System.out.println("链表查询" + (end - start)/1000000.0 + " ");
  43. start = System.nanoTime();
  44. // 二叉树搜索树
  45. bst.contains(999);
  46. // 红黑树
  47. // System.out.println(bst.containsKey(999));
  48. end = System.nanoTime();
  49. System.out.println("BST查询" + (end - start)/1000000.0 + " ");
  50. // System.out.println("红黑树查询" + (end - start)/1000000.0 + " ");
  51. }
  52. }

4.2.3 测试结果

二分搜索树与链表性能测试

红黑树与链表性能测试

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/528332
推荐阅读
相关标签
  

闽ICP备14008679号