二叉搜索树定义


二叉搜索树,是指一棵空树或者具有下列性质的二叉树:

  1. 若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
  2. 若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
  3. 任意节点的左,右子树也分别为二叉搜索树;
  4. 没有键值相等的节点。

用Java来表示二叉树


  1. public class BinarySearchTree
  2. { // 二叉搜索树类
  3. private class Node
  4. { // 节点类
  5. int data; // 数据域
  6. Node right; // 右子树
  7. Node left; // 左子树
  8. }
  9.  
  10. private Node root; // 树根节点
  11. }

首先,需要一个节点对象的类。这个对象包含数据域和指向节点的两个子节点的引用。

其次,需要一个树对象的类。这个对象包含一个根节点root。

创建树(insert)

  1. public void insert(int key)
  2. {
  3. Node p=new Node(); //待插入的节点
  4. p.data=key;
  5.  
  6. if(root==null)
  7. {
  8. root=p;
  9. }
  10. else
  11. {
  12. Node parent=new Node();
  13. Node current=root;
  14. while(true)
  15. {
  16. parent=current;
  17. if(key>current.data)
  18. {
  19. current=current.right; // 右子树
  20. if(current==null)
  21. {
  22. parent.right=p;
  23. return;
  24. }
  25. }
  26. else //本程序没有做key出现相等情况的处理,暂且假设用户插入的节点值都不同
  27. {
  28. current=current.left; // 左子树
  29. if(current==null)
  30. {
  31. parent.left=p;
  32. return;
  33. }
  34. }
  35. }
  36. }
  37. }

创建树的时候,主要用到了parent,current来记录要插入节点的位置。哪么怎么检验自己是否正确地创建了一颗二叉搜索树呢,我们通过遍历来输出各个节点的值

遍历树(travel)

遍历指的是按照某种特定的次序来访问二叉搜索树中的每个节点,主要有三种遍历的方法:

  1. 前序遍历,“中左右”
  2. 中序遍历,“左中右”
  3. 后续遍历,“左右中”

上面的口诀“中左右”表示的含义是,先访问根节点,再访问左子,最后访问右子。举个例子:

  • 前序遍历:39 24 23 30 64 53 60
  • 中序遍历:23 24 30 39 53 60 64
  • 后序遍历:23 30 24 60 53 64 39

你会发现,按照中序遍历的规则将一个二叉搜索树输入,结果为按照正序排列。

  1. public void preOrder(Node root)
  2. { // 前序遍历,"中左右"
  3. if (root != null)
  4. {
  5. System.out.print(root.data + " ");
  6. preOrder(root.left);
  7. preOrder(root.right);
  8. }
  9. }
  10.  
  11. public void inOrder(Node root)
  12. { // 中序遍历,"左中右"
  13. if (root != null)
  14. {
  15. inOrder(root.left);
  16. System.out.print(root.data + " ");
  17. inOrder(root.right);
  18. }
  19. }
  20.  
  21. public void postOrder(Node root)
  22. { // 后序遍历,"左右中"
  23. if (root != null)
  24. {
  25. postOrder(root.left);
  26. postOrder(root.right);
  27. System.out.print(root.data + " ");
  28. }
  29. }
  30.  
  31. public void traverse(int traverseType)
  32. { // 选择以何种方式遍历
  33. switch (traverseType)
  34. {
  35. case 1:
  36. System.out.print("preOrder traversal ");
  37. preOrder(root);
  38. System.out.println();
  39. break;
  40. case 2:
  41. System.out.print("inOrder traversal ");
  42. inOrder(root);
  43. System.out.println();
  44. break;
  45. case 3:
  46. System.out.print("postOrder traversal ");
  47. postOrder(root);
  48. System.out.println();
  49. break;
  50. }
  51. }

以上的代码采用递归的方式实现三种遍历,为了方便我们使用,又写了一个traverse函数来实现选择哪种方式进行树的遍历。

这会儿就可以写单元测试了,我们首先创建一个二叉搜索树,然后分别使用“前序”,“中序”,“后序”来遍历输出树的所有节点。

  1. public static void main(String[] args) //unit test
  2. {
  3. BinarySearchTree tree=new BinarySearchTree();
  4.  
  5. tree.insert(39);
  6. tree.insert(24);
  7. tree.insert(64);
  8. tree.insert(23);
  9. tree.insert(30);
  10. tree.insert(53);
  11. tree.insert(60);
  12.  
  13. tree.traverse(1);
  14. tree.traverse(2);
  15. tree.traverse(3);
  16. }

运行该单元测试,可以看到如下的结果:

查找节点(find)

  1. public Node find(int key)
  2. { // 从树中按照关键值查找元素
  3. Node current = root;
  4. while (current.data != key)
  5. {
  6. if (key > current.data)
  7. current = current.right;
  8. else
  9. current = current.left;
  10. if (current == null) return null;
  11. }
  12. return current;
  13. }
  14.  
  15. public void show(Node node)
  16. { //输出节点的数据域
  17. if(node!=null)
  18. System.out.println(node.data);
  19. else
  20. System.out.println("null");
  21. }

查找节点比较简单,如果找到节点则返回该节点,否则返回null。为了方便在控制台输出,我们有添加了一个show函数,用来输出节点的数据域。

删除节点(delete)

删除节点是二叉搜索树中,最复杂的一种操作,但是也不是特别难,我们分类讨论:

  • 要删除节点有零个孩子,即叶子节点

如图所示,只需要将parent.left(或者是parent.right)设置为null,然后Java垃圾自动回收机制会自动删除current节点。

  • 要删除节点有一个孩子

如图所示,只需要将parent.left(或者是parent.right)设置为curren.right(或者是current.left)即可。

  • 要删除节点有两个孩子

这种情况比较复杂,首先我们引入后继节点的概念,如果将一棵二叉树按照中序周游的方式输出,则任一节点的下一个节点就是该节点的后继节点。例如:上图中24的后继节点为25,64的后继节点为70.找到后继节点以后,问题就变得简单了,分为两种情况:

1.后继节点为待删除节点的右子,只需要将curren用successor替换即可,注意处理好current.left和successor.right.

注意:这种情况下,successor一定没有左孩子,一但它有左孩子,哪它必然不是current的后继节点。

2.后继节点为待删除结点的右孩子的左子树,这种情况稍微复杂点,请看动态图片演示。

算法的步骤是:

  1. successorParent.left=successor.right
  2. successor.left=current.left
  3. parent.left=seccessor

弄懂原理后,我们来看具体的代码实现:

  1. private Node getSuccessor(Node delNode) //寻找要删除节点的中序后继结点
  2. {
  3. Node successorParent=delNode;
  4. Node successor=delNode;
  5. Node current=delNode.right;
  6.  
  7. //用来寻找后继结点
  8. while(current!=null)
  9. {
  10. successorParent=successor;
  11. successor=current;
  12. current=current.left;
  13. }
  14.  
  15. //如果后继结点为要删除结点的右子树的左子,需要预先调整一下要删除结点的右子树
  16. if(successor!=delNode.right)
  17. {
  18. successorParent.left=successor.right;
  19. successor.right=delNode.right;
  20. }
  21. return successor;
  22. }
  23.  
  24. public boolean delete(int key) // 删除结点
  25. {
  26. Node current = root;
  27. Node parent = new Node();
  28. boolean isRightChild = true;
  29. while (current.data != key)
  30. {
  31. parent = current;
  32. if (key > current.data)
  33. {
  34. current = current.right;
  35. isRightChild = true;
  36. }
  37. else
  38. {
  39. current = current.left;
  40. isRightChild = false;
  41. }
  42. if (current == null) return false; // 没有找到要删除的结点
  43. }
  44. // 此时current就是要删除的结点,parent为其父结点
  45. // 要删除结点为叶子结点
  46. if (current.right == null && current.left == null)
  47. {
  48. if (current == root)
  49. {
  50. root = null; // 整棵树清空
  51. }
  52. else
  53. {
  54. if (isRightChild)
  55. parent.right = null;
  56. else
  57. parent.left = null;
  58. }
  59. return true;
  60. }
  61. //要删除结点有一个子结点
  62. else if(current.left==null)
  63. {
  64. if(current==root)
  65. root=current.right;
  66. else if(isRightChild)
  67. parent.right=current.right;
  68. else
  69. parent.left=current.right;
  70. return true;
  71. }
  72. else if(current.right==null)
  73. {
  74. if(current==root)
  75. root=current.left;
  76. else if(isRightChild)
  77. parent.right=current.left;
  78. else
  79. parent.left=current.left;
  80. return true;
  81. }
  82. //要删除结点有两个子结点
  83. else
  84. {
  85. Node successor=getSuccessor(current); //找到要删除结点的后继结点
  86.  
  87. if(current==root)
  88. root=successor;
  89. else if(isRightChild)
  90. parent.right=successor;
  91. else
  92. parent.left=successor;
  93.  
  94. successor.left=current.left;
  95. return true;
  96. }
  97. }

二叉搜索树删除操作

大家注意哪个私有函数getSuccessor的功能,它不仅仅是用来找后继结点的。

总结

二叉搜索树其实不是特别难,理解以后,多练习几次,应该可以掌握。以下是全部的代码:

  1. package org.yahuian;
  2.  
  3. public class BinarySearchTree
  4. { // 二叉搜索树类
  5. private class Node
  6. { // 节点类
  7. int data; // 数据域
  8. Node right; // 右子树
  9. Node left; // 左子树
  10. }
  11.  
  12. private Node root; // 树根节点
  13.  
  14. public void insert(int key)
  15. {
  16. Node p = new Node(); // 待插入的节点
  17. p.data = key;
  18.  
  19. if (root == null)
  20. {
  21. root = p;
  22. }
  23. else
  24. {
  25. Node parent = new Node();
  26. Node current = root;
  27. while (true)
  28. {
  29. parent = current;
  30. if (key > current.data)
  31. {
  32. current = current.right; // 右子树
  33. if (current == null)
  34. {
  35. parent.right = p;
  36. return;
  37. }
  38. }
  39. else // 本程序没有做key出现相等情况的处理,暂且假设用户插入的节点值都不同
  40. {
  41. current = current.left; // 左子树
  42. if (current == null)
  43. {
  44. parent.left = p;
  45. return;
  46. }
  47. }
  48. }
  49. }
  50. }
  51.  
  52. public void preOrder(Node root)
  53. { // 前序遍历,"中左右"
  54. if (root != null)
  55. {
  56. System.out.print(root.data + " ");
  57. preOrder(root.left);
  58. preOrder(root.right);
  59. }
  60. }
  61.  
  62. public void inOrder(Node root)
  63. { // 中序遍历,"左中右"
  64. if (root != null)
  65. {
  66. inOrder(root.left);
  67. System.out.print(root.data + " ");
  68. inOrder(root.right);
  69. }
  70. }
  71.  
  72. public void postOrder(Node root)
  73. { // 后序遍历,"左右中"
  74. if (root != null)
  75. {
  76. postOrder(root.left);
  77. postOrder(root.right);
  78. System.out.print(root.data + " ");
  79. }
  80. }
  81.  
  82. public void traverse(int traverseType)
  83. { // 选择以何种方式遍历
  84. switch (traverseType)
  85. {
  86. case 1:
  87. System.out.print("preOrder traversal ");
  88. preOrder(root);
  89. System.out.println();
  90. break;
  91. case 2:
  92. System.out.print("inOrder traversal ");
  93. inOrder(root);
  94. System.out.println();
  95. break;
  96. case 3:
  97. System.out.print("postOrder traversal ");
  98. postOrder(root);
  99. System.out.println();
  100. break;
  101. }
  102. }
  103.  
  104. public Node find(int key)
  105. { // 从树中按照关键值查找元素
  106. Node current = root;
  107. while (current.data != key)
  108. {
  109. if (key > current.data)
  110. current = current.right;
  111. else
  112. current = current.left;
  113. if (current == null) return null;
  114. }
  115. return current;
  116. }
  117.  
  118. public void show(Node node)
  119. { //输出节点的数据域
  120. if(node!=null)
  121. System.out.println(node.data);
  122. else
  123. System.out.println("null");
  124. }
  125.  
  126. private Node getSuccessor(Node delNode) //寻找要删除节点的中序后继结点
  127. {
  128. Node successorParent=delNode;
  129. Node successor=delNode;
  130. Node current=delNode.right;
  131.  
  132. //用来寻找后继结点
  133. while(current!=null)
  134. {
  135. successorParent=successor;
  136. successor=current;
  137. current=current.left;
  138. }
  139.  
  140. //如果后继结点为要删除结点的右子树的左子,需要预先调整一下要删除结点的右子树
  141. if(successor!=delNode.right)
  142. {
  143. successorParent.left=successor.right;
  144. successor.right=delNode.right;
  145. }
  146. return successor;
  147. }
  148.  
  149. public boolean delete(int key) // 删除结点
  150. {
  151. Node current = root;
  152. Node parent = new Node();
  153. boolean isRightChild = true;
  154. while (current.data != key)
  155. {
  156. parent = current;
  157. if (key > current.data)
  158. {
  159. current = current.right;
  160. isRightChild = true;
  161. }
  162. else
  163. {
  164. current = current.left;
  165. isRightChild = false;
  166. }
  167. if (current == null) return false; // 没有找到要删除的结点
  168. }
  169. // 此时current就是要删除的结点,parent为其父结点
  170. // 要删除结点为叶子结点
  171. if (current.right == null && current.left == null)
  172. {
  173. if (current == root)
  174. {
  175. root = null; // 整棵树清空
  176. }
  177. else
  178. {
  179. if (isRightChild)
  180. parent.right = null;
  181. else
  182. parent.left = null;
  183. }
  184. return true;
  185. }
  186. //要删除结点有一个子结点
  187. else if(current.left==null)
  188. {
  189. if(current==root)
  190. root=current.right;
  191. else if(isRightChild)
  192. parent.right=current.right;
  193. else
  194. parent.left=current.right;
  195. return true;
  196. }
  197. else if(current.right==null)
  198. {
  199. if(current==root)
  200. root=current.left;
  201. else if(isRightChild)
  202. parent.right=current.left;
  203. else
  204. parent.left=current.left;
  205. return true;
  206. }
  207. //要删除结点有两个子结点
  208. else
  209. {
  210. Node successor=getSuccessor(current); //找到要删除结点的后继结点
  211.  
  212. if(current==root)
  213. root=successor;
  214. else if(isRightChild)
  215. parent.right=successor;
  216. else
  217. parent.left=successor;
  218.  
  219. successor.left=current.left;
  220. return true;
  221. }
  222. }
  223.  
  224. public static void main(String[] args) // unit test
  225. {
  226. BinarySearchTree tree = new BinarySearchTree();
  227.  
  228. tree.insert(39);
  229. tree.insert(24);
  230. tree.insert(64);
  231. tree.insert(23);
  232. tree.insert(30);
  233. tree.insert(53);
  234. tree.insert(60);
  235.  
  236. tree.traverse(1);
  237. tree.traverse(2);
  238. tree.traverse(3);
  239.  
  240. tree.show(tree.find(23));
  241. tree.show(tree.find(60));
  242. tree.show(tree.find(64));
  243.  
  244. tree.delete(23);
  245. tree.delete(60);
  246. tree.delete(64);
  247.  
  248. tree.show(tree.find(23));
  249. tree.show(tree.find(60));
  250. tree.show(tree.find(64));
  251. }
  252. }

二叉搜索树详解


动态图片来自于:https://visualgo.net/en/bst

二叉搜索树详解(Java实现)的更多相关文章

  1. 数据结构图文解析之:二叉堆详解及C++模板实现

    0. 数据结构图文解析系列 数据结构系列文章 数据结构图文解析之:数组.单链表.双链表介绍及C++模板实现 数据结构图文解析之:栈的简介及C++模板实现 数据结构图文解析之:队列详解与C++模板实现 ...

  2. 7-4 是否同一棵二叉搜索树 (25分) JAVA

    给定一个插入序列就可以唯一确定一棵二叉搜索树.然而,一棵给定的二叉搜索树却可以由多种不同的插入序列得到. 例如分别按照序列{2, 1, 3}和{2, 3, 1}插入初始为空的二叉搜索树,都得到一样的结 ...

  3. 剑指Offer:面试题27——二叉搜索树与双向链表(java实现)

    问题描述: 输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表.要求不能创建任何新的结点,只能调整树中结点指针的指向. 思路: 将树分为三部分:左子树,根结点,右子树. 1.我们要把根结点与左 ...

  4. 95题--不同的二叉搜索树II(java、中等难度)

    题目描述:给定一个整数 n,生成所有由 1 ... n 为节点所组成的 二叉搜索树 . 示例如下: 分析:这一题需要对比LeetCode96题来分析:https://www.cnblogs.com/K ...

  5. AVL树(二叉平衡树)详解与实现

    AVL树概念 前面学习二叉查找树和二叉树的各种遍历,但是其查找效率不稳定(斜树),而二叉平衡树的用途更多.查找相比稳定很多.(欢迎关注数据结构专栏) AVL树是带有平衡条件的二叉查找树.这个平衡条件必 ...

  6. 《剑指offer》面试题27 二叉搜索树与双向链表 Java版

    (将BST改成排序的双向链表.) 我的方法一:根据BST的性质,如果我们中序遍历BST,将会得到一个从小到大排序的序列.如果我们将包含这些数字的节点连接起来,就形成了一个链表,形成双向链表也很简单.关 ...

  7. 二叉搜索树(BST)详解

    前言:平衡树的前置知识吧 二叉搜索树的定义: 二叉搜索树或者是一棵空树,或者是具有下列性质的二叉树: (1)若左子树不空,则左子树上所有结点的值均小于或等于它的根节点的值: (2)若右子树不空,则右子 ...

  8. 【算法与数据结构】二叉搜索树的Java实现

    为了更加深入了解二叉搜索树,博主自己用Java写了个二叉搜索树,有兴趣的同学可以一起探讨探讨. 首先,二叉搜索树是啥?它有什么用呢? 二叉搜索树, 也称二叉排序树,它的每个节点的数据结构为1个父节点指 ...

  9. Java实现二叉搜索树的添加,前序、后序、中序及层序遍历,求树的节点数,求树的最大值、最小值,查找等操作

    什么也不说了,直接上代码. 首先是节点类,大家都懂得 /** * 二叉树的节点类 * * @author HeYufan * * @param <T> */ class Node<T ...

随机推荐

  1. Invalid YGDirection 'vertical'. should be one of: ( inherit, ltr, rtl )

    react native 路由( react-native-router-flux )跳转页面一直都报错 本项目解决方法:不是路由的问题,是跳转的页面有有问题,删除下图标记的红色即可(解决方法是排除法 ...

  2. 牛客假日团队赛2 A.买一送一

    链接: https://ac.nowcoder.com/acm/contest/924/A 题意: Farmer John在网上买干草.他发现了一笔特殊的买卖.他每买一捆大小为A(1 <= A ...

  3. NET Core 1.1 静态文件、路由、自定义中间件、身份验证简介

    NET Core 1.1 静态文件.路由.自定义中间件.身份验证简介   概述 之前写过一篇关于<ASP.NET Core 1.0 静态文件.路由.自定义中间件.身份验证简介>的文章,主要 ...

  4. Linux下无法挂载U盘

    大概的错误信息是这样的: Error mounting /dev/sdb4 at /media/xxx/xx: Command-line`mount -t "ntfs" -o&qu ...

  5. Apache Atlas是什么?

    不多说,直接上干货! Apache Atlas是Hadoop社区为解决Hadoop生态系统的元数据治理问题而产生的开源项目,它为Hadoop集群提供了包括数据分类.集中策略引擎.数据血缘.安全和生命周 ...

  6. Aspose.word直接转pdf

    using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.We ...

  7. iOS - NSString 封装

    在实际项目开发过程中,发现字符串使用频率还是非常高的,NSString提供了很多相关的API,但是在开发过程中发现很多业务功能都是相同的.因此根据在开发过程中遇到的字符串使用场景,进行了简单封装.具体 ...

  8. SlickEdit 18.0 版本发布 同时更新破解文件

    18.0版本没有太大的惊喜 多了如下功能 Multiple Document Group Interface Repository Log Browser History Diff Support f ...

  9. 如何正确配置 Nginx + PHP ???

    本文转自如何正确配置 Nginx + PHP,如有侵权,请联系管理员及时删除!

  10. IT部门域事件与业务分析

    IT event--->system--->IT dept |--------------->IT dept |--------------->system 域事件分类: 直接 ...