树的遍历方式

  • 前序遍历:前根遍历,按照根左右的顺序遍历
  • 中序遍历:中根遍历,按照左根右的顺序遍历
  • 后序遍历:后根遍历,按照左右根的顺序遍历

二叉排序树的概念

二叉排序树(Binary Sort Tree),又称二叉查找树(Binary Search Tree),也称二叉搜索树。
二叉排序树或者是一颗空树,或者是具有下列性质的二叉树:

  • 若左子树不空,则左子树上所有结点的值均小于或等于它的根结点的值
  • 若右子树不空,则右子树上所有结点的值均大与或等于它的根结点的值
  • 左、右子树也分别为二叉排序树
  • 二叉排序树中每次新插入的结点一定是叶子结点。
  • 二叉排序树经过中序遍历后得到一个递增的有序序列。
  • 二叉排序树中的元素不重复。

查找

由于二叉树的递归定义性质,二叉排序树的查找同样可以使用如下递归算法查找:

  • 如果树是空的,则查找结束,无匹配。
  • 如果被查找的值和根结点的值相等,查找成功。否则就在子树中继续查找。
  • 如果被查找的值小于根结点的值就选择左子树,大于根结点的值就选择右子树。

在理想情况下,每次比较过后,树会被砍掉一半,近乎折半查找。使用中序遍历打印树,
打印出来的结果是从小到大的有序数组。

  1. /**
  2. * 二叉排序树的搜索:
  3. * 如果存在则返回此结点,如果不存在则返回查找路径上最后一个结点
  4. *
  5. * @param key
  6. * @param tree
  7. * @return
  8. */
  9. public BinarySearchTree search(int key, BinarySearchTree tree) {
  10. if (tree == null) {
  11. return null;
  12. }
  13. if (key == tree.key) {
  14. return tree;
  15. } else if (key < tree.key) {
  16. return tree.left != null ? search(key, tree.left) : tree;
  17. } else {
  18. return tree.right != null ? search(key, tree.right) : tree;
  19. }
  20. }

插入

二叉排序的插入是建立在二叉排序的查找之上的,插入一个结点,就是通过查找发现该结点合适插入位置,把结点直接放进去。二叉排序树插入规则如下:

  • 若查找的key已经有在树中,则point指向该数据结点。(二叉树的key不能重复)
  • 若查找的Key没有在树中,则point指向查找路径上最后一个结点,根据情况插入最后一个结点的左子树或右子树。

图解:
Screenshot from 2020-03-31 15-01-51.png
如果插入 56,先根据查找算法发现树中存在则不做操作,
如果插入 54,先根据查找算法没有找到k=54的结点,返回point指向53结点,发现53是叶子结点,且54 > 53,则把54插入到53的右子结点。
Screenshot from 2020-03-31 15-06-46.png

  1. /**
  2. * 二叉排序树的插入: 不插入重复的key
  3. *
  4. * @param key
  5. * @return
  6. */
  7. public void insert(int key) {
  8. BinarySearchTree sub = search(key, this);
  9. if (key != sub.key) {
  10. BinarySearchTree node = new BinarySearchTree(key);
  11. if (key > sub.key) {
  12. sub.right = node;
  13. } else {
  14. sub.left = node;
  15. }
  16. }
  17. }

删除

删除时需要考虑三种情况:

  • 要删除的结点是叶子结点
  • 要删除的结点只有左结点或右结点
  • 要删除的结点既有左结点又有右结点

图解:
第一种情况:要删除的结点是叶子结点
Screenshot from 2020-03-31 17-09-32.png
删除的结点是叶子结点时(left = null, right = null),则直接把双亲结点的指针移除。如上图所示,如果删除的是12结点,则29结点的left = null

  1. /**
  2. * 删除
  3. *
  4. * @param key
  5. */
  6. public void delete(int key) {
  7. BinarySearchTree node = search(key, this);
  8. if (node != null) {
  9. if (node.left == null && node.right == null) {
  10. BinarySearchTree parent = this.searchParent(key);
  11. if (parent.left != null && parent.left.equals(node)) {
  12. parent.left = null;
  13. }
  14. if (parent.right != null && parent.right.equals(node)) {
  15. parent.right = null;
  16. }
  17. }
  18. }
  19. }

第二种情况:要删除的结点只有左结点,这种情况下也分两种情况

  • 要删除的结点为根结点,需要将根结点的root指针指向即将删除结点的左孩子,然后将删除结点的left置空即可

Screenshot from 2020-03-31 18-35-06.png

  • 要删除的结点不为根结点,将父结点相应的孩子指针指向删除结点的左孩子,然后将删除结点的left置空

Screenshot from 2020-03-31 18-51-43.png

第三种情况:要删除的结点只有右结点,这种情况下也有两种情况同上
Screenshot from 2020-03-31 18-54-46.png
Screenshot from 2020-03-31 18-54-51.png

第四种情况:要删除的结点既有左结点又有右结点
查找删除结点右子树中最小的那个值(或者是左子树中最大的那个值),也就是右子树中位于最左方的那个结点(或者左子树中最右方的结点)。把查找的结点覆盖删除的结点。

Screenshot from 2020-04-01 09-14-55.png

  1. /**
  2. * 删除
  3. *
  4. * @param key
  5. */
  6. public void delete(int key) {
  7. BinarySearchTree node = search(key, this);
  8. if (node != null) {
  9. BinarySearchTree parent = this.searchParent(key);
  10. // 叶子结点
  11. if (node.left == null && node.right == null) {
  12. if (parent == null) {
  13. // 只有一个根结点的树,不能再删除
  14. } else {
  15. if (parent.left != null && parent.left.equals(node)) {
  16. parent.left = null;
  17. }
  18. if (parent.right != null && parent.right.equals(node)) {
  19. parent.right = null;
  20. }
  21. }
  22. }
  23. // 只有左结点
  24. if (node.left != null && node.right == null) {
  25. if (parent == null) {
  26. // 此结点为根结点
  27. this.key = node.key;
  28. this.left = node.left;
  29. this.right = node.right;
  30. } else {
  31. if (parent.left != null && parent.left.equals(node)) {
  32. parent.left = node.left;
  33. }
  34. if (parent.right != null && parent.right.equals(node)) {
  35. parent.right = node.left;
  36. }
  37. }
  38. }
  39. // 只有右结点
  40. if (node.right != null && node.left == null) {
  41. if (parent == null) {
  42. // 此结点为根结点
  43. this.key = node.key;
  44. this.left = node.left;
  45. this.right = node.right;
  46. } else {
  47. if (parent.left != null && parent.left.equals(node)) {
  48. parent.left = node.right;
  49. }
  50. if (parent.right != null && parent.right.equals(node)) {
  51. parent.right = node.right;
  52. }
  53. }
  54. }
  55. // 既有左结点又有右结点
  56. if (node.right != null && node.left != null) {
  57. // 使用方法一:查找删除结点的右子树最小的结点,然后用查找的结点替换删除的结点
  58. BinarySearchTree min = node.right.getMin();
  59. this.delete(min.key);
  60. node.key = min.key;
  61. }
  62. }
  63. }

完整代码:

  1. package com.bestlink.jh018782.tree;
  2. /**
  3. * 二叉排序树
  4. *
  5. * @author jeffrey
  6. * @date 3/31/20 3:14 PM
  7. */
  8. public class BinarySearchTree {
  9. private Integer key;
  10. private BinarySearchTree left;
  11. private BinarySearchTree right;
  12. public Integer getKey() {
  13. return key;
  14. }
  15. public BinarySearchTree getLeft() {
  16. return left;
  17. }
  18. public BinarySearchTree getRight() {
  19. return right;
  20. }
  21. public BinarySearchTree() {
  22. }
  23. public BinarySearchTree(int obj) {
  24. this.key = obj;
  25. }
  26. public BinarySearchTree(int[] data) {
  27. this.key = data[0];
  28. for (int i = 1; i < data.length; i++) {
  29. insert(data[i]);
  30. }
  31. }
  32. /**
  33. * 二叉排序树的搜索:
  34. * 如果存在则返回此结点,如果不存在则返回查找路径上最后一个结点
  35. *
  36. * @param key
  37. * @param tree
  38. * @return
  39. */
  40. public BinarySearchTree search(int key, BinarySearchTree tree) {
  41. if (tree == null) {
  42. return null;
  43. }
  44. if (key == tree.key) {
  45. return tree;
  46. } else if (key < tree.key) {
  47. return tree.left != null ? search(key, tree.left) : tree;
  48. } else {
  49. return tree.right != null ? search(key, tree.right) : tree;
  50. }
  51. }
  52. /**
  53. * 查找父结点
  54. */
  55. public BinarySearchTree searchParent(int key) {
  56. if ((this.left != null && this.left.key == key) || (this.right != null && this.right.key == key)) {
  57. return this;
  58. } else {
  59. if (this.key > key && this.left != null) {
  60. return this.left.searchParent(key);
  61. } else if (this.key < key && this.right != null) {
  62. return this.right.searchParent(key);
  63. }
  64. }
  65. return null;
  66. }
  67. /**
  68. * 二叉排序树的插入: 不插入重复的key
  69. *
  70. * @param key
  71. * @return
  72. */
  73. public void insert(int key) {
  74. BinarySearchTree sub = search(key, this);
  75. if (key != sub.key) {
  76. BinarySearchTree node = new BinarySearchTree(key);
  77. if (key > sub.key) {
  78. sub.right = node;
  79. } else {
  80. sub.left = node;
  81. }
  82. }
  83. }
  84. /**
  85. * 删除
  86. *
  87. * @param key
  88. */
  89. public void delete(int key) {
  90. BinarySearchTree node = search(key, this);
  91. if (node != null) {
  92. BinarySearchTree parent = this.searchParent(key);
  93. // 叶子结点
  94. if (node.left == null && node.right == null) {
  95. if (parent == null) {
  96. // 只有一个根结点的树,不能再删除
  97. } else {
  98. if (parent.left != null && parent.left.equals(node)) {
  99. parent.left = null;
  100. }
  101. if (parent.right != null && parent.right.equals(node)) {
  102. parent.right = null;
  103. }
  104. }
  105. }
  106. // 只有左结点
  107. if (node.left != null && node.right == null) {
  108. if (parent == null) {
  109. // 此结点为根结点
  110. this.key = node.key;
  111. this.left = node.left;
  112. this.right = node.right;
  113. } else {
  114. if (parent.left != null && parent.left.equals(node)) {
  115. parent.left = node.left;
  116. }
  117. if (parent.right != null && parent.right.equals(node)) {
  118. parent.right = node.left;
  119. }
  120. }
  121. }
  122. // 只有右结点
  123. if (node.right != null && node.left == null) {
  124. if (parent == null) {
  125. // 此结点为根结点
  126. this.key = node.key;
  127. this.left = node.left;
  128. this.right = node.right;
  129. } else {
  130. if (parent.left != null && parent.left.equals(node)) {
  131. parent.left = node.right;
  132. }
  133. if (parent.right != null && parent.right.equals(node)) {
  134. parent.right = node.right;
  135. }
  136. }
  137. }
  138. // 既有左结点又有右结点
  139. if (node.right != null && node.left != null) {
  140. // 使用方法一:查找删除结点的右子树最小的结点,然后用查找的结点替换删除的结点
  141. BinarySearchTree min = node.right.getMin();
  142. this.delete(min.key);
  143. node.key = min.key;
  144. }
  145. }
  146. }
  147. /**
  148. * 递归查询结点的最左侧结点(最小的结点)
  149. * @return
  150. */
  151. public BinarySearchTree getMin() {
  152. if (this.left == null) {
  153. return this;
  154. } else {
  155. return this.left.getMin();
  156. }
  157. }
  158. /**
  159. * 递归查询结点的最右侧结点(最大的结点)
  160. * @return
  161. */
  162. public BinarySearchTree getMax() {
  163. if (this.right == null) {
  164. return this;
  165. } else {
  166. return this.right.getMax();
  167. }
  168. }
  169. /**
  170. * 获取树的深度
  171. *
  172. * @param tree
  173. * @return
  174. */
  175. public int getDepth(BinarySearchTree tree) {
  176. return tree == null ? 0 : (1 + Math.max(getDepth(tree.left), getDepth(tree.right)));
  177. }
  178. /**
  179. * 打印整颗树
  180. */
  181. public void print() {
  182. // 得到树的深度
  183. int treeDepth = getDepth(this);
  184. // 最后一行的宽度为2的(n - 1)次方乘3,再加1
  185. // 作为整个二维数组的宽度
  186. int arrayHeight = treeDepth * 2 - 1;
  187. int arrayWidth = (2 << (treeDepth - 2)) * 3 + 1;
  188. // 用一个字符串数组来存储每个位置应显示的元素
  189. String[][] res = new String[arrayHeight][arrayWidth];
  190. // 对数组进行初始化,默认为一个空格
  191. for (int i = 0; i < arrayHeight; i++) {
  192. for (int j = 0; j < arrayWidth; j++) {
  193. res[i][j] = " ";
  194. }
  195. }
  196. // 从根节点开始,递归处理整个树
  197. // res[0][(arrayWidth + 1)/ 2] = (char)(root.val + '0');
  198. writeArray(this, 0, arrayWidth / 2, res, treeDepth);
  199. // 此时,已经将所有需要显示的元素储存到了二维数组中,将其拼接并打印即可
  200. for (String[] line : res) {
  201. StringBuilder sb = new StringBuilder();
  202. for (int i = 0; i < line.length; i++) {
  203. sb.append(line[i]);
  204. if (line[i].length() > 1 && i <= line.length - 1) {
  205. i += line[i].length() > 4 ? 2 : line[i].length() - 1;
  206. }
  207. }
  208. System.out.println(sb.toString());
  209. }
  210. }
  211. private static void writeArray(BinarySearchTree currNode, int rowIndex, int columnIndex, String[][] res, int treeDepth) {
  212. // 保证输入的树不为空
  213. if (currNode == null) {
  214. return;
  215. }
  216. // 先将当前节点保存到二维数组中
  217. res[rowIndex][columnIndex] = String.valueOf(currNode.key);
  218. // 计算当前位于树的第几层
  219. int currLevel = ((rowIndex + 1) / 2);
  220. // 若到了最后一层,则返回
  221. if (currLevel == treeDepth) {
  222. return;
  223. }
  224. // 计算当前行到下一行,每个元素之间的间隔(下一行的列索引与当前元素的列索引之间的间隔)
  225. int gap = treeDepth - currLevel - 1;
  226. // 对左儿子进行判断,若有左儿子,则记录相应的"/"与左儿子的值
  227. if (currNode.left != null) {
  228. res[rowIndex + 1][columnIndex - gap] = "/";
  229. writeArray(currNode.left, rowIndex + 2, columnIndex - gap * 2, res, treeDepth);
  230. }
  231. // 对右儿子进行判断,若有右儿子,则记录相应的"\"与右儿子的值
  232. if (currNode.right != null) {
  233. res[rowIndex + 1][columnIndex + gap] = "\\";
  234. writeArray(currNode.right, rowIndex + 2, columnIndex + gap * 2, res, treeDepth);
  235. }
  236. }
  237. }

测试用例:

  1. package com.bestlink.jh018782;
  2. import com.bestlink.jh018782.tree.BinarySearchTree;
  3. import org.junit.jupiter.api.Test;
  4. /**
  5. * @author jeffrey
  6. * @date 3/31/20 3:56 PM
  7. */
  8. public class BinarySearchTreeTest {
  9. @Test
  10. public void test() {
  11. int[] arr = new int[]{60, 48, 73, 29, 56, 95, 12, 33, 53, 86, 100};
  12. // 转换
  13. BinarySearchTree tree = new BinarySearchTree(arr);
  14. tree.print();
  15. System.out.println();
  16. // 插入
  17. tree.insert(90);
  18. tree.insert(31);
  19. tree.print();
  20. // 删除
  21. tree.delete(95);
  22. tree.print();
  23. }
  24. }