二叉排序树的创建与添加操作(思路分析)

二叉排序树的创建与添加操作(思路分析)

首先我们要创建一个二叉排序树就要先创建二叉排序树的结点类, 然后创建好了二叉树排序树结点类之后就可以创建二叉排序树类了

思维开阔:

当我们创建好了二叉排序树类之后我们就要编写一系列的方法, 而二叉树这个章节中有90%的问题我们都是使用递归来实现的, 比如此时假如我们要编写一个添加元素的方法, 那么我们就要使用递归来实现, 我们就要在二叉排序树里中编写一个add(int val)方法 —> 此时我们的添加元素的方法就是我们传入一个整数即可, 当传入一个整数之后我们就在这个方法中将这个整数作为二叉排序树结点类的value属性值封装为一个对应的结点, 然后将这个节点添加到我们的二叉排序树中

  • 这个时候我们要执行的添加元素的操作我们也是使用递归来实现的, 这个时候既然我们要使用递归来实现, 那么我们就有两种具体的实现思维(因为我们递归的时候就要编写一个递归方法, 而add(int value)方法并不适合作为递归方法, 因为此时我们要实现二叉排序树添加元素操作的时候是要遍历二叉树的, 所以我们这个时候肯定是要在方法的形参位置传入一个Node类型的实参, 这个实参每次都是变化的, 或者我们就要确保这个方法的调用者是一个Node类型的实参):

    1. 第一种方式就是在二叉排序树类中再编写一个add(Node node , int val)方法, 使用这个方法作为具体的递归方法, 我们第一次的时候向第一个形参的位置传入我们的根节点即可, 然后后面的时候就递归方式一直向下遍历即可, 每次遍历的过程中做出响应的判断, 最终找到我们的添加元素的位置, 然后创建一个value属性值为val 的二叉排序树结点, 将这个节点加到二叉树中即可
    2. 第二种方式就是我们在二叉排序树结点类中编写这个递归的方法, 这个时候我们就可以不向形参的位置传值了, 因为如果我们将递归方法编写到结点类中的时候我们的这个方法的调用者就时Node类型的引用, 所以每次的时候我们只需要改变这个方法的调用者即可
      • 那么这个时候有的人就会有问题了, 我们每次的时候都改变方法的调用者, 但是我们是要在方法中获取到这个方法的调用者的, 那么我们如何在方法的内部获取到方法的调用者?
        • 其实很简单, 在我们的方法中我们this关键字其实就是指向的我们的方法的调用者
    • 上面的这两种方式具体的情况之下我们都是可以使用的, 这两种方式其实实际比较的话是没有太大的区别的

创建一个二叉排序树的思路分析:

创建一个二叉排序树其实是很简单的, 因为我们前面是已经创建了二叉树结点类的, 所以此时我们就只需要创建一个成员变量root作为二叉排序树的根节点, 然后再提供一个构造犯法即可

向二叉排序树中添加元素的思路分析:

第一种方式: (就是采用我们上面的思维开阔中的第一种方式)
  • 这个时候我们就假设我们添加元素的操作的时候就是直接传入一个int型的值,并且我们使用的是上面的思维开阔中的第一种方式, 就是在二叉排序树类中提供了一个额外的递归调用方法add(Node node , int val)
  1. 首先这个时候我们添加操作是使用递归来实现的, 那么我们的递归中最开始要考虑的一点就是递归的终止条件, 我们此时的递归的终止条件显然就是当我们的node结点为 null的之后就终止, 否则就一直递归, 然后进行比较

    • 当我们最终找到一个为null的结点之后这个位置其实就是我们的结点要添加的位置了, 此时我们就要在这一层栈中创建我们的待添加的结点, 然后将这个待添加的结点返回
      • 这个时候我们要考虑, 这个时候我们执行了return操作之后返回了我们的待插入的结点, 然后到了上一层栈中, 此时的node结点指向的是我们遍历过的倒数第二个节点, 此时我们就要将我们的返回的结点的添加到我们倒数第二个节点的左子节点或者是右子节点的位置上, 这个时候具体的我们要添加到左子节点或者是右子节点的位置上我们是要看我们上一次递归的时候是向左递归的还是向右递归的, 如递归的时候是向右递归的, 那么这个时候我们就要将这个结点添加到左子节点的位置, 如果是向右递归的, 这个时候就要将我们的结点添加到我们的右子节点的位置
  2. 我们首先是要明确我们是要从根节点开始遍历的, 所以我们最开始的时候要在向递归方法中的第一个参数位置传入我们的二叉排序树的根节点

  3. 然后判断我们传入的val的值和根节点中的value属性值的大小, 如果这个时候我们的传入的val的值要比我们的根节点的value属性值要大, 这个时候我们就要向右子树上遍历, 然后一直遍历知道遍历到一个为空的位置之后这位置就时我们最终要添加结点的位置

    • 这个时候有一个问题: 如果我们传入的val的值等于我们的某个结点的值的时候我们要如何执行操作, 我们前面的时候都是如果val的值大于当前节点的value属性的之后就到右子树上去查找, 如果val的值小于当前节点的value属性的时候就去到左子树上去查找, 但是很显然我们是没有判断如果我们的val的是等于我们当前节点的value属性的情况的

      • 那么如果我们的val的值等于我们的当前节点的value属性值的时候我们要如何操作?

        • 这个时候其实我们既可以向左子树上递归, 也可以向右子树上去递归, 这个时候我们都是可以的, 因为我们的二叉排序树的概念中说的是我们的当前子树的根节点的值要大于所有的左子树上的结点的值, 并且要小于所有的右子树上的结点的值,----> 也就是说我们的二叉排序树中其实严格意义上来讲是不能有相同的值的结点的, 但是在实际情况中我们对于相同值的情况显然又是不可以避免的, 这个时候所以我们其实将这个值是添加到左子树或者是右子树上其实都是可以的

          • 但是要注意:如果我们创建二叉排序树的是将我们的相同的值添加到了我们的右子树上, 这个时候如果我们执行其他的操作的时候我们要判断相同的值的情况的时候就一定也要去右子树上进行一个判断
上述方式的缺点:

我们其实可以发现这种方式的执行效率其实是并不高的, 因为这种方式的实现中我们每次递归时候不仅仅是进行了递归调用, 每次的时候我们还都是要对我们遍历过的所有的结点进行一个拼接的 , 因为这个时候我们实现的是递归的思路,而我们的递归实现的时候就是采用的"统一的思想" , 我们很显然对于最后一次操作的时候我们就会找到最后我们要添加的元素的位置, 这个时候我们就会创建一个二叉排序树结点, 然后我们要将这个节点返回并且拼接到我们的二叉树中, 这个时候我们要对我们的最后一个创建的结点进行一个这样的操作,那么对应的我们就要同样的对前面的结点也进行一个这样的操作, 所以这样的方式其实就无形中降低了我们的程序的执行效率

  • 因为我们实际中其实是只要最后一次我们创建的新结点是需要添加到我们的二叉树中的 ,但是很显然我们每次的时候都执行了这样的一个拼接操作, 所以就降低了我们的执行效率, 但是也只是对效率有一点影响, 因为我们使用第二种方式的时候每次递归中其实也是要多做一个判断的, 其实时间复杂度都是O(n),所以效率相差不大
    • 如果我们使用的是思维扩展中的第二中方式来实现添加的操作的时候就不会有这样的问题了
上述方式的优点:

使用这种方式的优点就是我们使用这种方式的时候不需要对根节点为空的情况作出特殊判断, 为什么?

  • 我们可以将我们的根节点为空的情况带入:

    • 当我们的根节点为空的时候其实我们就直接会进入到递归的结束条件中, 这个时候直接就会创建对应的结点,并且这个时候我们创建了对应的结点之后就会将这个节点返回, 最终的之后我们只要在编写的二叉排序树类的添加元素的方法中将这个返回值接收下来并且赋给root根节点即可 —> 所以最终就是当我们的根节点为空的时候其实就是直接创建了一个新的节点之后将这个新的结点赋值给了我们的根节点
  • 这种方式其实就是将头结点的情况也是通过形参的方式传入了, 所以我们对于头结点为空的情况直接就在递归终止条件中判断了, 其实此时递归终止条件就是对入参的判断, 也就是对头结点的特殊处理

第二种方式: (就是采用我们的思维扩展中的第二种方式)

  • 我们假设这种方式之下我们也是传入一个int值, 然后直接就进行添加操作
  1. 这种方式我们是在二叉树结点类中编写了一个添加元素的递归方法, 我们每次都改变这个递归方法的调用者, 我们在这个方法中使用this关键字获取到这个方法的调用者并做相应的操作
    • 也就是在二叉排序树结点类中声明一个如下的方法:
      • add(int val)
  2. 我们开始的时候也是从头结点开始遍历, 最开始的时候就是在二叉排序树中的添加元素的方法中使用root引用来调用我们的结点类中的添加元素的方法, 所以我们在对应的结点类中的添加元素的方法中的this关键字就是代表的头结点
    • 注意: 这个时候在二叉排序树类中的添加元素的方法中调用我们的结点类中的添加元素的方法的时候我们是使用的root引用来进行的调用, 为了避免我们的root结点为null的情况, 这个时候我们要进行一个引用类型数据的判断, 要确保这个root引用不为null, 如果为null的时候我们进行一个特殊判断
  3. 然后我们判断val值和当前this的value属性值的大小, 如果val值比当前value属性值大, 并且判断val.right是否为null, 如果不是null , 那么就使用val.right去调用递归方法, 如果val值比当前value属性值小, 并且判断val.right是否为null, 如果不是null , 那么就使用val.left去调用递归方法, 知道判断出某个结点的left或者right为null的时候就退出, 所以这个递归的条件就是当当前节点的left或者right为null的时候,
    • 这个时候为null的位置就是待插入节点, 所以我们只要让此时为null的当前节点.left或者当前节点.right指向新创建的待插入节点即可
这种方式要在对外提供的方法中对根节点做一个特殊性判断, 因为这种方式其实我们就是在二叉排序树类中提供的添加结点的方法中使用了引用数据调用了其中的方法, 所以就要对这个引用数据进行一个合理性判断

补充:

我们是否可以在方法中获取到我们的方法的调用者? 如果可以获取到的话要如何获得?

  • 答案是可以获得的, 我们在一个成员方法中使用this关键字其实就是代表的方法的调用者
    • 我们在学习this关键字的时候就提及到过: this关键字在我们的构造方中其实就是表示的当前正在构建的对象, 在成员方法中this关键字就是表示的调用当前的成员方法的对象

补充二:

我们编写一个方法的时候只需要判断入参就可以了(也就是判断传入的形参变量就可以了), 不需要判断这个方法的调用者是否合法, 对于方法的调用者的判断应该是要在调用此方法的位置去判断这个方法的调用者是否合法

  • 所以说: 我们在一个方法中有两点是需要判断的:
    1. 判断入参是否合法(也就是对入参做一个判断)
    2. 在使用引用类型数据调用一个属性或者一个方法的时候要判断这个引用数据是否合法, 其中最常见的就是判断这个引用是否为null
      • 其实绝大多数的时候我们都是在判断这个引用数据是否为null, 但是其实还是有其他的情况的, 但是其他的情况发生的几率比较小, 比如我们使用的引用数据中根本就没有对应的属性或者是方法
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值