1. 原创:转载需注明原创地址 https://www.cnblogs.com/fanerwei222/p/11406176.html

  尝试一下用Java实现二叉搜索树/二叉查找树,记录自己的学习历程。

  1. 首先先来设计实现一下节点Node。

  一个二叉树的节点需要以下几个元素:

    key 关键字

    value 节点的值(key也可以代替value)

    parent 父节点

    leftChildren 左儿子节点

    rightChildren 右儿子节点

  那就开始吧!

  

  1. /**
  2. * 节点
  3. */
  4. class Node{
  5. private int key;
  6. private String value;
  7. private Node parent;
  8. private Node leftChildren;
  9. private Node rightChildren;
  10.  
  11. public Node(){}
  12.  
  13. public Node(int key, String value){
  14. this.key = key;
  15. this.value = value;
  16. }
  17.  
  18. public int getKey() {
  19. return key;
  20. }
  21.  
  22. public void setKey(int key) {
  23. this.key = key;
  24. }
  25.  
  26. public Node getParent() {
  27. return parent;
  28. }
  29.  
  30. public void setParent(Node parent) {
  31. this.parent = parent;
  32. }
  33.  
  34. public Node getLeftChildren() {
  35. return leftChildren;
  36. }
  37.  
  38. public void setLeftChildren(Node leftChildren) {
  39. this.leftChildren = leftChildren;
  40. }
  41.  
  42. public Node getRightChildren() {
  43. return rightChildren;
  44. }
  45.  
  46. public void setRightChildren(Node rightChildren) {
  47. this.rightChildren = rightChildren;
  48. }
  49.  
  50. public String getValue() {
  51. return value;
  52. }
  53.  
  54. public void setValue(String value) {
  55. this.value = value;
  56. }
  57. }

  2. 接下来就是树的实现了。

  一个二叉树会有什么操作呢?

    find(int key) 根据key查找对应的节点

    insert(int key, String value) 插入节点

    preOrder(Node root) 前序遍历

    midOrder(Node root) 中序遍历

    backOrder(Node root) 后序遍历

    delete(int key) 根据key删除节点

  好了,需要的东西都知道了,开始实现吧!先新建一个BinarySearchTree.java文件,把其他属性和方法写上。

  

  1. /**
  2. * 二叉查找树/二叉搜索树
  3. */
  4. public class BinarySearchTree {
  5. private Node root;
  6. /**
  7. * 遍历结果集合
  8. */
  9. List<Node> orderResult = new ArrayList<>();
  10.  
  11. public BinarySearchTree(Node root){
  12. this.root = root;
  13. }
  14.  
  15. public BinarySearchTree(){}
  16.  
  17. public Node getRoot(){
  18. return this.root;
  19. }
  20. }

  一步步往里面加入上面列出的方法并且一一实现吧!

  ️find(int key)

  1. /**
  2. * 查找key
  3. * @param key
  4. * @return
  5. */
  6. public Node find(int key){
  7. /**
  8. * 从根节点开始找
  9. */
  10. Node currentNode = root;
  11. /**
  12. * 判断根节点是否符合要求
  13. */
  14. while (currentNode != null && key != currentNode.getKey()){
  15. if (key < currentNode.getKey()){
  16. //to left
  17. currentNode = currentNode.getLeftChildren();
  18. }else {
  19. //to right
  20. currentNode = currentNode.getRightChildren();
  21. }
  22. }
  23.  
  24. return currentNode;
  25. }

  首先把root节点设置为当前节点,当当前节点不为null而且当前节点的key和传入的key不相等时,我们继续循环,判断这个key的node节点是在当前节点的左子树还是右子树,并且继续把左子树或者右子树设为当前节点继续执行,直到当前节点是null或者当前节点的key值等于传入的key值,返回当前节点,查找结束。

  

  ️insert(int key, String value)

  1. /**
  2. * 插入
  3. */
  4. public void insert(int key, String value){
  5. Node newNode = new Node(key, value);
  6. if (null == root){
  7. this.root = newNode;
  8. return ;
  9. }
  10. /**
  11. * 当前搜索到的树
  12. */
  13. Node currentNode = root;
  14. Node parentNode = root;
  15. /**
  16. * 默认左子树
  17. */
  18. boolean isLeftChild = true;
  19. while (null != currentNode){
  20. parentNode = currentNode;
  21. if (key < parentNode.getKey()){
  22. //父节点左侧
  23. currentNode = parentNode.getLeftChildren();
  24. isLeftChild = true;
  25. }else {
  26. //父节点右侧
  27. currentNode = parentNode.getRightChildren();
  28. isLeftChild = false;
  29. }
  30. }
  31.  
  32. /**
  33. * 循环结束之后的parentNode就是最终需要插入子节点的节点,根据isLeftChild判断插入左儿子还是右儿子
  34. */
  35. Node childNode = new Node(key, value);
  36. if (isLeftChild){
  37. parentNode.setLeftChildren(childNode);
  38. childNode.setParent(parentNode);
  39. }else {
  40. parentNode.setRightChildren(childNode);
  41. childNode.setParent(parentNode);
  42. }
  43. return ;
  44. }

  二叉查找树的插入是将比父节点的值插入到左子树里面,大的放右子树去找到位置插入。

  首先,我们new一个节点,看看root节点存不存在,不存在就设置new的节点为root节点,插入结束。

  第二种情况,有root节点,继续往下执行,将root节点赋给currentNode变量和parentNode变量,后面采用cn和pn缩写;

  我们就默许先从左儿子节点开始比较,key小于pn的key,把cn的左儿子节点赋给cn,继续循环,每次循环开始都把cn保存为pn起来,这样的话,直到cn为null,我们可以轻松获取到上一次操作的cn,也就是当前的pn。

  循环结束后,开始插入新节点:

  根据isLeftChild判断插入左边还是右边,插入的时候记得给父节点设置儿子,给儿子设置父节点,方便我们后续进行删除操作。

  

  ️遍历,有前中后三个遍历方法,记住前中后是对父节点来说的就很好办了!

    * 前序遍历就是对每个树遍历的时候把父节点拎到最前面;

    * 中序遍历就是对每个树遍历的时候把父节点拎到最中间;

    * 后序遍历就是对每个树遍历的时候把父节点拎到最后面。

  代码很简单,递归思想解决。不明白的话可以自己执行debug一遍就深刻理解了!100遍的解释不如自己debug一次!

  

  1. /**
  2. * 前序遍历
  3. * @param root
  4. * @return
  5. */
  6. public List<Node> preOrder(Node root){
  7. if (null == root){
  8. return null;
  9. }
  10. orderResult.add(root);
  11. preOrder(root.getLeftChildren());
  12. preOrder(root.getRightChildren());
  13. return orderResult;
  14. }
  15.  
  16. /**
  17. * 中序遍历
  18. * @param root
  19. * @return
  20. */
  21. public List<Node> midOrder(Node root){
  22. if (null == root){
  23. return null;
  24. }
  25. midOrder(root.getLeftChildren());
  26. orderResult.add(root);
  27. midOrder(root.getRightChildren());
  28.  
  29. return orderResult;
  30. }
  31.  
  32. /**
  33. * 后序遍历
  34. * @param root
  35. * @return
  36. */
  37. public List<Node> backOrder(Node root){
  38. if (null == root){
  39. return null;
  40. }
  41. backOrder(root.getLeftChildren());
  42. backOrder(root.getRightChildren());
  43. orderResult.add(root);
  44.  
  45. return orderResult;
  46. }

  ️删除,最难的部分来了,这部分是最麻烦的,不过一步一步来,很多麻烦事都可以解决的,不用害怕。

  先贴删除方法和涉及到的方法代码:

  

  1. /**
  2. * 删除节点(有三种情况)
  3. * 1.待删除节点没有子节点
  4. * 2.待删除节点只有一个子节点
  5. * 3.待删除节点有两个子节点
  6. * @param key
  7. * @return
  8. */
  9. public boolean delete(int key){
  10. /**
  11. * 被查找到的节点
  12. */
  13. Node findNode = find(key);
  14.  
  15. if (null == findNode){
  16. return false;
  17. }
  18. /**
  19. * 待删除节点没有儿子节点
  20. */
  21. if (null == findNode.getLeftChildren() && null == findNode.getRightChildren()){
  22. if (null == findNode.getParent()){
  23. root = null;
  24. return true;
  25. }else {
  26. /**
  27. * 是否和父节点的左儿子相同
  28. */
  29. if (isParentLeftChildren(findNode, key)){
  30. findNode.getParent().setLeftChildren(null);
  31. return true;
  32. }else{
  33. /**
  34. * 右儿子
  35. */
  36. findNode.getParent().setRightChildren(null);
  37. return true;
  38. }
  39. }
  40. }
  41.  
  42. /**
  43. * 待删除节点左儿子存在,右儿子为空
  44. */
  45. if (null == findNode.getRightChildren() && null != findNode.getLeftChildren()){
  46. if (isParentLeftChildren(findNode, key)){
  47. findNode.getParent().setLeftChildren(findNode.getLeftChildren());
              findNode.getLeftChildren().setParent(findNode.getParent());
  48. findNode = null;
  49. return true;
  50. }else{
  51. findNode.getParent().setRightChildren(findNode.getLeftChildren());
              findNode.getLeftChildren().setParent(findNode.getParent());
  52. findNode = null;
  53. return true;
  54. }
  55. }
  56. /**
  57. * 待删除节点右儿子存在,左儿子为空
  58. */
  59. else if (null == findNode.getLeftChildren() && null != findNode.getRightChildren()){
  60. if (isParentLeftChildren(findNode, key)){
  61. findNode.getParent().setLeftChildren(findNode.getRightChildren());
  62. findNode.getRightChildren().setParent(findNode.getParent());
  63. findNode = null;
  64. return true;
  65. }else{
  66. findNode.getParent().setRightChildren(findNode.getRightChildren());
  67. findNode.getRightChildren().setParent(findNode.getParent());
  68. findNode = null;
  69. return true;
  70. }
  71. }
  72. /**
  73. * 待删除节点左右儿子都存在
  74. */
  75. else if (null != findNode.getLeftChildren() && null != findNode.getRightChildren()){
  76. if (isParentLeftChildren(findNode, key)){
  77. /**
  78. * 设置一系列的引用
  79. */
  80. Node succesor = findSuccessorNode(findNode);
  81. /**
  82. * 判断后继者是否就是当前节点的左儿子,如果是,不需要再重复设置自己引用自己,会引发内存泄漏。
  83. */
  84. if (succesor != findNode.getLeftChildren()){
  85. findNode.getLeftChildren().setParent(succesor);
  86. succesor.setLeftChildren(findNode.getLeftChildren());
  87. findNode.getRightChildren().setParent(succesor);
  88. succesor.setRightChildren(findNode.getRightChildren());
  89. findNode.getParent().setLeftChildren(succesor);
  90. succesor.setParent(findNode.getParent());
  91. findNode = null;
  92.  
  93. return true;
  94. }else {
  95. findNode.getRightChildren().setParent(succesor);
  96. succesor.setRightChildren(findNode.getRightChildren());
  97. findNode.getParent().setLeftChildren(succesor);
  98. succesor.setParent(findNode.getParent());
  99. findNode = null;
  100.  
  101. return true;
  102. }
  103.  
  104. }else{
  105. Node succesor = findSuccessorNode(findNode);
  106. /**
  107. * 判断后继者是否就是当前节点的左儿子,如果是,不需要再重复设置自己引用自己,会引发内存泄漏。
  108. */
  109. if (succesor != findNode.getLeftChildren()){
  110. findNode.getLeftChildren().setParent(succesor);
  111. succesor.setLeftChildren(findNode.getLeftChildren());
  112. findNode.getRightChildren().setParent(succesor);
  113. succesor.setRightChildren(findNode.getRightChildren());
  114. findNode.getParent().setRightChildren(succesor);
  115. succesor.setParent(findNode.getParent());
  116. findNode = null;
  117.  
  118. return true;
  119. }else {
  120. findNode.getRightChildren().setParent(succesor);
  121. succesor.setRightChildren(findNode.getRightChildren());
  122. findNode.getParent().setRightChildren(succesor);
  123. succesor.setParent(findNode.getParent());
  124. findNode = null;
  125.  
  126. return true;
  127. }
  128. }
  129. }
  130.  
  131. return false;
  132. }
  133.  
  134. /**
  135. * 查询该节点是否父节点的左节点
  136. * @param findNode
  137. * @param key
  138. */
  139. private boolean isParentLeftChildren(Node findNode,int key){
  140. /**
  141. * 是否和父节点的左儿子相同
  142. */
  143. if (null != findNode.getParent().getLeftChildren() && findNode.getParent().getLeftChildren().getKey() == key){
  144. return true;
  145. }else{
  146. /**
  147. * 是否和父节点的右儿子相同
  148. */
  149. return false;
  150. }
  151. }
  152.  
  153. /**
  154. * 查找待删除节点的后继节点,一般是右儿子的左子树的最小值
  155. * @param node
  156. * @return
  157. */
  158. private Node findSuccessorNode(Node node){
  159. /**
  160. * 保存父节点
  161. */
  162. Node parentNode = node;
  163. /**
  164. * 当前节点
  165. */
  166. Node currentNode = node;
  167. while (null != currentNode){
  168. parentNode = currentNode;
  169. currentNode = currentNode.getLeftChildren();
  170. }
  171. /**
  172. * 判断父节点的右儿子是否为空,不为空的话需要把右儿子提到该父节点的父节点(没有重复打,就是两个父节点)的左儿子
  173. */
  174. if (null == parentNode.getRightChildren()){
  175. return parentNode;
  176. }else {
  177. parentNode.getParent().setLeftChildren(parentNode.getRightChildren());
           parentNode.getRightChildren().setParent(parentNode.getParent());
  178. return parentNode;
  179. }
  180. }

  至此!代码全部贴完!分析完删除部分,再把我的main测试方法代码贴出来,删除方法可能不是很完整,思想大致如此,第一次不是很缜密,下次改进!

  *删除分三种情况:

    1. 待删除节点没有儿子节点

    2.待删除节点只有一个儿子节点

    3.待删除节点有两个儿子节点

  需要分别针对这三种情况处理!

  虽然代码都写了注释,不过还是自己再重新理一遍吧!开干!(看自己写的代码真是觉得要多烂有多烂,头疼)

  情况1 :待删除节点没有儿子节点

    首先看看待删除节点有没有父节点,没有父节点那就是root节点,设置root为null就行了。

    然后看看待删除节点是不是和父节点的左儿子相同,是的话将父节点的左儿子设为null,不是的话将右儿子设为null就行了。

    情况1结束。

  情况2 :待删除节点只有一个儿子节点,但是还不知道是左儿子还是右儿子。

    1) 左儿子节点存在

      先isParentLeftChildren()看看待删除节点是它父节点的左儿子还是右儿子,如果是左儿子,将待删除节点的左儿子节点设置为待删除节点父节点的左儿子,反之亦然。

    2)右儿子节点存在

      先isParentLeftChildren()看看待删除节点是它父节点的左儿子还是右儿子,如果是左儿子,将待删除节点的左儿子节点设置为待删除节点父节点的右儿子,反之亦然。

  情况3 :待删除节点右两个儿子(这种情况略微复杂)

    先isParentLeftChildren()看看待删除节点是它父节点的左儿子还是右儿子,如果是左儿子,继续

      因为待删除节点的两个儿子都存在,说明要从两个子树中挑一个节点来做待删除节点的继承者!

        查找继承者用findSuccessorNode()方法;首先查找到左子树的最左侧的最小的儿子,判断这个最小的儿子是不是有右儿子,没有右儿子的情况下直接返回这个最小的儿子,

        如果这个最小的儿子有右儿子,需要把这个最小的儿子的右儿子设置为这个最小的儿子的父节点的左儿子,再返回这个最小的儿子作为继承者。

      继承者找到了。这里再判断一下继承者是不是就是当前节点的左儿子,是的话就不必重复设置继承者的父节点为待删除节点的左儿子也就是继承者,不是的话,设置引用。

      因为这里前面isParentLeftChildren()找到的是左儿子,所以设置待删除节点的父节点的左儿子为继承者,待删除节点的右儿子设置为继承者的右儿子。

    反之,亦然。

   下面的一大段分析看不明白就结合代码来看,代码里面的注释也有很多的,文章写到这里就结束了!

   结束

Java实现二叉搜索树的更多相关文章

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

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

  2. Java创建二叉搜索树,实现搜索,插入,删除操作

    Java实现的二叉搜索树,并实现对该树的搜索,插入,删除操作(合并删除,复制删除) 首先我们要有一个编码的思路,大致如下: 1.查找:根据二叉搜索树的数据特点,我们可以根据节点的值得比较来实现查找,查 ...

  3. Java数据结构——二叉搜索树

    定义二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值: 若 ...

  4. Java实现二叉搜索树及相关操作

    package com.tree; import com.tree.BitNode; /** * * 二叉搜索树:一个节点的左子节点的关键字小于这个节点.右子节点的关键字大于或等于这个父节点 * * ...

  5. Java实现二叉搜索树的插入、删除

    前置知识 二叉树的结构 public class TreeNode { int val; TreeNode left; TreeNode right; TreeNode() { } TreeNode( ...

  6. Java对二叉搜索树进行插入、查找、遍历、最大值和最小值的操作

    1.首先,须要一个节点对象的类.这些对象包括数据.数据代表存储的内容,并且还有指向节点的两个子节点的引用 class Node { public int iData; public double dD ...

  7. 二叉搜索树Java实现(查找、插入、删除、遍历)

    由于最近想要阅读下 JDK1.8 中 HashMap 的具体实现,但是由于 HashMap 的实现中用到了红黑树,所以我觉得有必要先复习下红黑树的相关知识,所以写下这篇随笔备忘,有不对的地方请指出- ...

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

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

  9. Java与算法之(13) - 二叉搜索树

    查找是指在一批记录中找出满足指定条件的某一记录的过程,例如在数组{ 8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15 }中查找数字15,实现代码很简单 ...

随机推荐

  1. Oracle对表空间、用户、用户权限的操作

    一.对表空间的操作 1.创建表空间(create tablespace) -- 'hpdb_tablespace' 指定表空间名称 -- 'e:\hpdb.dbf' 指定表空间数据文件名称 -- si ...

  2. Swoole 协程使用示例及协程优先级

    示例一: Co::set(['hook_flags'=> SWOOLE_HOOK_ALL]); Co\run(function () { go(function() { var_dump(fil ...

  3. Docker_容器(container)使用(4)

    参数说明 -i: 交互式操作. -t: 终端. -d: 指定容器运行模式. --name:指定容器的NAMES字段名称,不指定则随机生成名称 --restart:容器启动策略.默认为no,常用为alw ...

  4. Flask + flask_sqlalchemy + jq 完成书籍展示、新增、删除功能

    后端代码 from flask import Flask, render_template, request, jsonify from flask_wtf.csrf import CSRFProte ...

  5. 第10组 Alpha冲刺 总结(组长)

    1.基本情况 组长博客链接:https://www.cnblogs.com/cpandbb/p/14007413.html 答辩总结: ·产品偏离了最开始的方向,地图和刷一刷功能做得没那么好,外卖订单 ...

  6. Zuul的应用

    一.介绍 注:Zuul中默认就已经集成了Ribbon负载均衡和Hystix熔断机制.但是所有的超时策略都是走的默认值,比如熔断超时时间只有1S,很容易就触发了. 二.依赖 <dependency ...

  7. What's The Next|Kube-OVN 社区线上 Meetup 预告!

    ​ ​ Kube-OVN 社区线上Meetup 直播预约通道已开启! 活动时间 2021年8月26日(周四)19:00-20:30 活动介绍 8月26日,Kube-OVN 社区 Meetup 将通过线 ...

  8. 【Java】==与equals

    ==与equals 一. == 可以使用在基本数据类型变量和引用数据类型变量中 如果比较的是基本数据类型变量:比较两个变量保存的数据是否相等.(不一定类型要相同) 如果比较的是引用数据类型变量:比较两 ...

  9. PIKACHU之文件包含漏洞

    PIKUCHU靶场之文件包含 一.file inclusion(local) 实验源码: <?php /** * Created by runner.han * There is nothing ...

  10. leetcode 347. 前 K 个高频元素

    问题描述 给定一个非空的整数数组,返回其中出现频率前 k 高的元素.   示例 1: 输入: nums = [1,1,1,2,2,3], k = 2 输出: [1,2] 示例 2: 输入: nums ...