前言

前面介绍学习的大多是线性表相关的内容,把指针搞懂后其实也没有什么难度。规则相对是简单的。

再数据结构中才是数据结构标志性产物,(线性表大多都现成api可以使用),因为树的难度相比线性表大一些并且树的拓展性很强,你所知道的树、二叉树、二叉排序树,AVL树,线索二叉树、红黑树、B数、线段树等等高级数据结构。然而二叉排序树是所有的基础,所以彻底搞懂二叉排序树也是非常重要的。

参考王道数据结构

二叉树也是树的一种,而二叉排序树又是二叉树的一种。

  • 树是递归的,将树的任何一个节点以及节点下的节点都能组合成一个新的树。并且很多操作基于递归完成。
  • 根节点: 最上面的那个节点(root),根节点没有前驱节点,只有子节点(0个或多个都可以)
  • 层数: 一般认为根节点是第1层(有的也说第0层)。而树的高度就是层数最高(上图层数开始为1)节点的层数
  • 节点关系: 父节点:就是链接该节点的上一层节点,孩子节点:和父节点对应,上下关系。而祖先节点是父节点的父节点(或者祖先)节点。兄弟节点:拥有同一个父节点的节点们!
  • 度: 节点的度就是节点拥有孩子节点的个数(是孩子不是子孙).而树的度(最大)节点的度。同时,如果度大于0就成为分支节点,度等于0就成为叶子节点(没有子孙)。

相关性质:

  • 树的节点数=所有节点度数+1.
  • 度为m的树第i层最多有mi-1个节点。(i>=1)
  • 高度而h的m叉树最多(mh-1)/(m-1)个节点(等比数列求和)
  • n个节点的m叉树最小高度[logm(n(m-1)+1)]

二叉树

二叉树是一树的一种,但应用比较多,所以需要深入学习。二叉树的每个节点最多只有两个节点

二叉树与度为2的树的区别:

  • 一:度为2的的树必须有三个节点以上,二叉树可以为空。
  • 二:二叉树的度不一定为2:比如说斜树。
  • 三:二叉树有左右节点区分,而度为2的树没有左右节点的区分。

几种特殊二叉树:

  • 满二叉树。高度为n的满二叉树有2n-1个节点
  • 完全二叉树:上面一层全部满,最下一层从左到右顺序排列
  • 二叉排序树:树按照一定规则插入排序(本文详解)。
  • 平衡二叉树:树上任意节点左子树和右子树深度差距不超过1.

二叉树性质:
相比树,二叉树的性质就是树的性质更加具体化。

  • 非空二叉树叶子节点数=度为2的节点树+1.本来一个节点如果度为1.那么一直延续就一个叶子,但如果出现一个度为2除了延续原来的一个节点,会多出一个节点需要维系。所以到最后会多出一个叶子
  • 非空第i层最多有2i-1个节点。
  • 高为h的树最多有2h-1个节点(等比求和)。
  • 完全二叉树若从左往右,从上到下编号如图:

二叉排序(搜索)树


概念

前面铺垫那么多,咱们言归正传,详细实现一个二叉排序树。首先要了解二叉排序树的规则:

  • 从任意节点开始,节点左侧节点值总比节点右侧值要小。
    例如。一个二叉排序树依次插入15,6,23,7,4,71,5,50会形成下图顺序

构造

首先二叉排序树是由若干节点构成。

  • 对于node需要这些属性:left,right,和value。其中left和right是左右指针,而value是储存的数据,这里用int 类型。

node类构造为:

  1. class node {//结点
  2. public int value;
  3. public node left;
  4. public node right;
  5. public node()
  6. {
  7. }
  8. public node(int value)
  9. {
  10. this.value=value;
  11. this.left=null;
  12. this.right=null;
  13. }
  14. public node(int value,node l,node r)
  15. {
  16. this.value=value;
  17. this.left=l;
  18. this.right=r;
  19. }
  20. }

既然节点构造好了,那么就需要节点等其他信息构造成树。有了链表构造经验,很容易得知一棵树最主要的还是root根节点
所以树的构造为:

  1. public class BinarySortTree {
  2. node root;//根
  3. public BinarySortTree()
  4. {root=null;}
  5. public void makeEmpty()//变空
  6. {root=null;}
  7. public boolean isEmpty()//查看是否为空
  8. {return root==null;}
  9. //各种方法
  10. }

主要方法

  • 既然已经构造号一棵树,那么就需要实现主要的方法。因为二叉排序树中每个节点都能看作一棵树。所以我们创建方法的是时候加上节点参数(也就是函数对每一个节点都能有效)

findmax(),findmin()

findmin()找到最小节点:

  • 因为所有节点的最小都是往左插入,所以只需要找到最左侧的返回即可。

findmax()找到最大节点:

  • 因为所有节点大的都是往右面插入,所以只需要找到最右侧的返回即可。
    代码使用递归函数
  1. public node findmin(node t)//查找最小返回值是node,调用查看结果时需要.value
  2. {
  3. if(t==null) {return null;}
  4. else if(t.left==null) {return t;}
  5. else return(findmin(t.left));
  6. }
  7. public node findmax(node t)//查找最大
  8. {
  9. if(t==null) {return null;}
  10. else if(t.right==null) {return t;}
  11. else return(findmax(t.right));
  12. }

isContains(int x)

这里的意思是查找二叉查找树中是否存在x。

  • 假设我们我们插入x,那么如果存在x我们一定会在查找插入路径的过程中遇到x。因为你可以如果已经存在的点,再它的前方会走一次和它相同的步骤。也就是说前面固定,我来1w次x,那么x都会到达这个位置。那么我们直接进行查找比较即可!
  1. public boolean isContains(int x)//是否存在
  2. {
  3. node current=root;
  4. if(root==null) {return false;}
  5. while(current.value!=x&&current!=null)
  6. {
  7. if(x<current.value) {current=current.left;}
  8. if(x>current.value) {current=current.right;}
  9. if(current==null) {return false;}//在里面判断如果超直接返回
  10. }
  11. //如果在这个位置判断是否为空会导致current.value不存在报错
  12. if(current.value==x) {return true;}
  13. return false;
  14. }

insert(int x)

插入的思想和前面isContains类似。找到自己的位置(空位置)插入。但是又不太一样。你可能会疑问为什么不直接找到最后一个空,然后将current赋值过去current=new node(x)。这样的化current就相当于指向一个new node(x)节点。和树就脱离关系,所以要提前判定是否为空,若为空将它的left或者right赋值即可。

  1. public node insert(int x)// 插入 t是root的引用
  2. {
  3. node current = root;
  4. if (root == null) {
  5. root = new node(x);
  6. return root;
  7. }
  8. while (current != null) {
  9. if (x < current.value) {
  10. if (current.left == null) {
  11. return current.left = new node(x);}
  12. else current = current.left;}
  13. else if (x > current.value) {
  14. if (current.right == null) {
  15. return current.right = new node(x);}
  16. else current = current.right;
  17. }
  18. }
  19. return current;//其中用不到
  20. }
  • 比如说上面结构插入51

delete(int x)

删除操作算是一个相对较难理解的操作了。
删除节点规则:

  • 先找到这个点。这个点用这个点的子树可以补上的点填充该点,然后在以这个点为头删除替代的子节点(调用递归)然后在添加到最后情况(只有一个分支,等等)。
  • 首先要找到移除的位置,然后移除的那个点分类讨论,如果有两个儿子,就选右边儿子的最左侧那个点替代,然后再子树删除替代的那个点。如果是一个节点,判断是左空还是右空,将这个点指向不空的那个。不空的那个就替代了这个节点。入股左右都是空,那么他自己变空null就删除了。

删除的节点没有子孙:

  • 这种情况不需要考虑,直接删除即可。(途中红色点)。另节点=null即可。

左节点为空、右节点为空:

  • 此种情况也很容易,直接将删除点的子节点放到被删除位置即可。

左右节点均不空

  • 这种情况相对是复杂的。因为这涉及到一个策略问题。
  • 如果拿19或者71节点填补。虽然可以保证部分侧大于小于该节点,但是会引起合并的混乱.比如你若用71替代23节点。那么你需要考虑三个节点(19,50,75)之间如何处理,还要考虑他们是否满,是否有子女。这是个极其复杂的过程。
  • 首先,我们要分析我们要的这个点的属性:能够继承被删除点的所有属性。如果取左侧节点(例如17)那么首先能满足所有右侧节点都比他大(右侧比左侧大)。那么就要再这边选一个最大的点让左半枝都比它小。我们分析左支最大的点一定是子树最右侧
  • 如果这个节点是最底层我们很好考虑,可以直接替换值,然后将最底层的点删除即可。但是如果这个节点有左枝。我们该怎么办?
  • 这个分析起来也不难,用递归的思想啊。我们删除这个节点,用可以满足的节点替换了。会产生什么样的后果?
  • 多出个用过的19节点,转化一下,在左子树中删除19的点!那么这个问题又转化为删除节点的问题,查找左子树中有没有能够替代19这个点的。

所以整个删除算法流程为:

代码为

  1. public node remove(int x, node t)// 删除节点
  2. {
  3. if (t == null) {
  4. return null;
  5. }
  6. if (x < t.value) {
  7. t.left = remove(x, t.left);
  8. } else if (x > t.value) {
  9. t.right = remove(x, t.right);
  10. } else if (t.left != null && t.right != null)// 左右节点均不空
  11. {
  12. t.value = findmin(t.right).value;// 找到右侧最小值替代
  13. t.right = remove(t.value, t.right);
  14. } else // 左右单空或者左右都空
  15. {
  16. if (t.left == null && t.right == null) {
  17. t = null;
  18. } else if (t.right != null) {
  19. t = t.right;
  20. } else if (t.left != null) {
  21. t = t.left;
  22. }
  23. return t;
  24. }
  25. return t;
  26. }

完整代码

二叉排序树完整代码为:

  1. package 二叉树;
  2. import java.util.ArrayDeque;
  3. import java.util.Queue;
  4. import java.util.Stack;
  5. public class BinarySortTree {
  6. class node {// 结点
  7. public int value;
  8. public node left;
  9. public node right;
  10. public node() {
  11. }
  12. public node(int value) {
  13. this.value = value;
  14. this.left = null;
  15. this.right = null;
  16. }
  17. public node(int value, node l, node r) {
  18. this.value = value;
  19. this.left = l;
  20. this.right = r;
  21. }
  22. }
  23. node root;// 根
  24. public BinarySortTree() {
  25. root = null;
  26. }
  27. public void makeEmpty()// 变空
  28. {
  29. root = null;
  30. }
  31. public boolean isEmpty()// 查看是否为空
  32. {
  33. return root == null;
  34. }
  35. public node findmin(node t)// 查找最小返回值是node,调用查看结果时需要.value
  36. {
  37. if (t == null) {
  38. return null;
  39. } else if (t.left == null) {
  40. return t;
  41. } else
  42. return (findmin(t.left));
  43. }
  44. public node findmax(node t)// 查找最大
  45. {
  46. if (t == null) {
  47. return null;
  48. } else if (t.right == null) {
  49. return t;
  50. } else
  51. return (findmax(t.right));
  52. }
  53. public boolean isContains(int x)// 是否存在
  54. {
  55. node current = root;
  56. if (root == null) {
  57. return false;
  58. }
  59. while (current.value != x && current != null) {
  60. if (x < current.value) {
  61. current = current.left;
  62. }
  63. if (x > current.value) {
  64. current = current.right;
  65. }
  66. if (current == null) {
  67. return false;
  68. } // 在里面判断如果超直接返回
  69. }
  70. // 如果在这个位置判断是否为空会导致current.value不存在报错
  71. if (current.value == x) {
  72. return true;
  73. }
  74. return false;
  75. }
  76. public node insert(int x)// 插入 t是root的引用
  77. {
  78. node current = root;
  79. if (root == null) {
  80. root = new node(x);
  81. return root;
  82. }
  83. while (current != null) {
  84. if (x < current.value) {
  85. if (current.left == null) {
  86. return current.left = new node(x);}
  87. else current = current.left;}
  88. else if (x > current.value) {
  89. if (current.right == null) {
  90. return current.right = new node(x);}
  91. else current = current.right;
  92. }
  93. }
  94. return current;//其中用不到
  95. }
  96. public node remove(int x, node t)// 删除节点
  97. {
  98. if (t == null) {
  99. return null;
  100. }
  101. if (x < t.value) {
  102. t.left = remove(x, t.left);
  103. } else if (x > t.value) {
  104. t.right = remove(x, t.right);
  105. } else if (t.left != null && t.right != null)// 左右节点均不空
  106. {
  107. t.value = findmin(t.right).value;// 找到右侧最小值替代
  108. t.right = remove(t.value, t.right);
  109. } else // 左右单空或者左右都空
  110. {
  111. if (t.left == null && t.right == null) {
  112. t = null;
  113. } else if (t.right != null) {
  114. t = t.right;
  115. } else if (t.left != null) {
  116. t = t.left;
  117. }
  118. return t;
  119. }
  120. return t;
  121. }
  122. }

结语

  • 这里我们优先学习了树,二叉树,以及二叉搜素树的基本构造。对于二叉搜素树插入查找比较容易理解但是实现的时候要注意函数对参数的引用等等。需要认真考虑。
  • 而偏有难度的是二叉树的删除,利用一个递归的思想,要找到特殊情况和普通情况,递归一定程度也是问题的转化(转成自己相同问题,作用域减小)需要思考。
  • 下面还会介绍二叉搜素树的三序遍历(递归和非递归).和层序遍历。需要的朋友请持续关注。另外,笔者数据结构专栏欢迎查房。!
  • 如果对后端、爬虫、数据结构算法等感性趣欢迎关注我的个人公众号交流:bigsai。回复爬虫,数据结构等有精美资料一份。

数据结构与算法—二叉排序树(java)的更多相关文章

  1. 数据结构与算法【Java】08---树结构的实际应用

    前言 数据 data 结构(structure)是一门 研究组织数据方式的学科,有了编程语言也就有了数据结构.学好数据结构才可以编写出更加漂亮,更加有效率的代码. 要学习好数据结构就要多多考虑如何将生 ...

  2. 数据结构与算法【Java】02---链表

    前言 数据 data 结构(structure)是一门 研究组织数据方式的学科,有了编程语言也就有了数据结构.学好数据结构才可以编写出更加漂亮,更加有效率的代码. 要学习好数据结构就要多多考虑如何将生 ...

  3. 数据结构与算法【Java】03---栈

    前言 数据 data 结构(structure)是一门 研究组织数据方式的学科,有了编程语言也就有了数据结构.学好数据结构才可以编写出更加漂亮,更加有效率的代码. 要学习好数据结构就要多多考虑如何将生 ...

  4. 数据结构与算法【Java】05---排序算法总结

    前言 数据 data 结构(structure)是一门 研究组织数据方式的学科,有了编程语言也就有了数据结构.学好数据结构才可以编写出更加漂亮,更加有效率的代码. 要学习好数据结构就要多多考虑如何将生 ...

  5. 常用数据结构及算法C#/Java实现

    常用数据结构及算法C#实现 1.冒泡排序.选择排序.插入排序(三种简单非递归排序) ,, , , , , , , , , }; //冒泡排序 int length = waitSort.Length; ...

  6. 数据结构与算法之java语言实现(一):稀疏数组

    一.概念&引入 什么是稀疏数组? 稀疏数组是面对一个二维数组中有众多重复元素的情况下,为了节省磁盘空间,将此二维数组转化为更加节省空间的一种数组,我们叫他稀疏数组. 只是听概念或许会看不明白, ...

  7. 数据结构和算法(java版本)学习指南

    1 数据结构和算法内容介绍 2 数据结构和算法的概述 3 稀疏数组SparseArray

  8. 数据结构和算法(Java版)快速学习(线性表)

    线性表的基本特征: 第一个数据元素没有前驱元素: 最后一个数据元素没有后继元素: 其余每个数据元素只有一个前驱元素和一个后继元素. 线性表按物理存储结构的不同可分为顺序表(顺序存储)和链表(链式存储) ...

  9. 数据结构和算法(Java版)快速学习(数组Array)

    Java数组 在Java中,数组是用来存放同一种数据类型的集合,注意只能存放同一种数据类型. 用类封装数组实现数据结构 数据结构必须具有以下基本功能: ①.如何插入一条新的数据项 ②.如何寻找某一特定 ...

随机推荐

  1. 论样式表css的重要性

    如下图所示两个网页代码基本相同,但左边网页加入样式表后就形成了右边的视觉效果,由此可见 在网页中html用于标记,css用于显示,而JavaScript则用于增强与用户的交互性. 加入的代码是 < ...

  2. C++ Primer 第五版 一些遇到的注意点记录。

    第8章 8.2 p283 示例里有一句 ostream *old_tie = cin.tie(nullptr);//old_tie指向当前关联到cin的流 一开始不理解为什么不是无关联,查过tie() ...

  3. bugku安卓First_Mobile wp

    1 打开题目,下载apk 2 将下载好的apk拖进android killer中,提示文件名过长,随便更改一下文件名即可 3 查看入口文件源码(点击android killer工具栏咖啡杯图标) 4  ...

  4. java高并发系列 - 第17天:JUC中的循环栅栏CyclicBarrier常见的6种使用场景及代码示例

    这是java高并发系列第17篇. 本文主要内容: 介绍CyclicBarrier 6个示例介绍CyclicBarrier的使用 对比CyclicBarrier和CountDownLatch Cycli ...

  5. 如何在Windows上使用Python进行开发

    本文由葡萄城技术团队于原创并首发 转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 一直以来C#都是微软在编程语言方面最为显著的Tag,但时至今日Python ...

  6. 上车时机已到--.NETCore是适应时代发展的雄鹰利剑

    上车时机已到--.NETCore是适应时代发展的雄鹰利剑 要起飞了 .NET Core 3.0-prevew7:https://dotnet.microsoft.com/download/dotnet ...

  7. JNDI总结(一)

    一.数据源的由来 在Java开发中,使用JDBC操作数据库的四个步骤如下:   ①加载数据库驱动程序(Class.forName("数据库驱动类");)   ②连接数据库(Conn ...

  8. PHP 防范xss攻击(转载)

    XSS 全称为 Cross Site Scripting,用户在表单中有意或无意输入一些恶意字符,从而破坏页面的表现! 看看常见的恶意字符XSS 输入: 1.XSS 输入通常包含 JavaScript ...

  9. 思路重要or技术重要?

    1,思路串通代码的重要性 前段时间,同事在工作上出现一点难题,在技术大佬中看起来算是微不足道的一点小事,由于没有思路,代码也无从下手,他在百度上条框上搜索自己想要的答案,却始终没有比较理想的,大部分的 ...

  10. C++ protobuffer 前后端通信 简单应用

    后端发送多个protobuffer消息到前端,前端用socket监听,如何区分消息类型呢? //定义心跳包 DseHeartbeat _DseHeartbeat; DseHeartbeat _DseH ...