😸二叉搜索树的概念
二叉搜索树又名二叉排序树,一般具有以下性质:
- 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
- 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
- 它的左右子树也分别为二叉搜索树
将其具现为一个图就是这样
🐭二叉搜索树的查找
我们可以根据二叉搜索树的特性,它的每一个结点的左子树比根小右子树比根大来进行快速查找,当一个数比根结点小就往根结点的左边找,比根结点大就往右边找,每次都可以将搜索范围缩小一半,最多查找h(树的高度)次,一个相对平衡的二叉树搜索树的时间复杂度大概为O(logN),为什么要说相对平衡呢?🤔因为在极端情况下,假如一棵二叉树只有右子树,那么他就会变成一个链表,这时他的复杂度就会变成O(logN)。所以在实际使用中,要注意维护二叉搜索树的平衡性(形状),因此在二叉搜索树的基础上,就诞生出了一些更具平衡性的树如,红黑树,AVL树。
🐭二叉搜索树的排序
因为二叉搜索树的每一棵子树都满足左子树 < 根 < 右子树,所以我们可以进行中序遍历,这样就可以得到一个升序序列了,一个相对平衡的二叉树搜索树的时间复杂度大概为O(NlogN)
😸二叉搜索树的实现
🐱二叉搜索树的创建
具体方法和创建一棵二叉树一样,每棵树都要有左右子树,然后创建一个初始的根结点
🐱二叉搜索树的查找
为了方便我们先写一个判空方法😋
🐭思路:
- 从根节点开始一直向下遍历
- 当目标值大于当前结点的值,就往右子树中继续搜索
- 当目标值小于当前结点的值,就往左子树中继续搜索
- 当目标值刚好等于当前结点值,直接返回true表示已经找到
- 当遍历到最后依然每找到则返回false表示没找到
这里我来画图举个例子:
🐱二叉搜索树的插入
🐭思路:
二叉搜索树的插入只能插到叶子结点上,而且不能插入相同的结点
首先需要先判断树是否为空
树为空:
😊即根结点为空,直接插入就行
树不为空:
😊因为二叉树不像链表,直接遍历到最后一个结点然后将新结点连在最后一个结点的后就行,二叉搜索树可能要插在左边还可能插在右边,所以不能像链表那样直接cur.next != null找到最后一个结点,我们要先定义一个父节点,用来标记要插到的叶子结点的父节点,先找到这个父节点然后在判断,要插入的值和这个父节点的大小以判断是插在左边还是右边。
🐱🐱二叉搜素树的删除(难点)
🐭思路:
首先我们要先找到要删除的结点,并且还有记住他的父节点,方法和刚刚写的插入类似,大的往右找小的往左找,当找到后对结点进行删除,那么要怎么删除呢🧐?因为比较复杂,需要单独写一个方法😋。
//parent是待删除的结点的父结点,cur是要删除的结点
因为这是一个二叉树删除之后不能破坏它的结构所以不能像线性表那样直接删除,要分几种情况:
🐭要删除的结点左边为空cur.left==null
1.cur是根结点,则直接让root = cur.right
要删除的结点左边为空,并且是根结点所以直接让根结点等于其右子树就行
2. cur 不是 root,cur 是 parent.left,则 parent.left = cur.right
如图,因为要删除的结点没有左子树,并且是父节点的左子树,所以直接让父节点的左边连上cur的右边
3. cur 不是 root,cur 是 parent.right,则 parent.right = cur.right
和上一个类似,因为要删除的结点没有左子树,并且是父节点的右子树,所以直接让父节点的右边连上cur的右边
🐭要删除的结点右边为空cur.right == null
1. cur 是 root,则 root = cur.left
因为要删除的结点右边为空,并且是根结点所以直接让根结点等于根结点的左子树
2. cur 不是 root,cur 是 parent.left,则 parent.left = cur.left
因为cur是父节点的左边,并且cur没有右子树,所以直接让父节点的左边,连cur的左边
3. cur 不是 root,cur 是 parent.right,则 parent.right = cur.left
因为cur是父节点的右边,并且没有右子树,所以直接让父节点的右边连cur的左边
🐭要删除的结点左右都为空cur.left != null && cur.right != null
这种情况利用上面的方法依然可以实现,比如cur在父节点的右边,因为cur没有子树,所以不管父节点连cur的左边还是右边(左右都为null)都可以完成删除,cue在父节点左边也是一样的,所以这部分内容利用上面的代码也能实现
以上我们讨论的是要删除的结点右空的子树的情况,那么接下来我们要讨论没有子树的情况即:
🐭要删除的结点左右都不为空
当cur左右结点都不为空时就会比较麻烦,我们的老招式就不管用了😣😟比如这样:
这该让父节点怎么连?🤔好像不管怎么连都会破坏树的结构🧐那么索性就不连了,因为当树的结构比较复杂时这样很容易就会破坏树的结构,那么就不删了吗🧐当然不是,只是用的方法不是删除,而是替代。如果我用一个合适的数去把我要删除的这个数给替代掉,然后再将这个数原来所在的结点删除,不就间接的完成了删除而且不会破坏树的结构了吗😉,用的这个方法也可以叫做寻找替罪羊。
思路:
既然我们在替代后,还要删除掉原来结点那么我们找的替罪羊(用来覆盖cur的结点)必须容易删除要不然就没有意义,所以替罪羊至少具有两点,1结点左右子树必须有空的,2大小必须合适,要满足二叉搜索树左边比结点小右边比结点大的特性。
左右子树必有空,只需要向下查找总能找到,那么我们现在要思考的是怎么找到大小合适的结点?
根据二叉搜索树的的特性我们有两种方法可以找到
- 在cur的左边找最大值
- 在cur的右边找最小值
这里我就使用在cur的右边找最小值来演示,因为cur的右子树都是比cur大的数,所以我们就要在其右子树中找到比cur.right小的数就行,那么只需要找cur.right的左子树不就好了😉,既比cur大(因为在cur右边),又比cur.right小(因为在cur左边)
所以根据上面的分析我们知道了我们要找的结点是cur.right的左子树,并且这个替罪羊结点有空的子树,如果能找到,这个空的结点必然是左子树(如果左子树还有结点证明还可以向下查找)最后用这个结点覆盖掉cur,再删掉就行😉
//在cur的左边找最大值,同理找cur.left的右子树如果能找到,他的右子树必然是空的
//如果替罪羊结点就是cur.right就不能再往左边找了,因为这时左边已经为空了,所以要单独判断一下
😸性能分析
二叉搜索树插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。 对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度 的函数,即结点越深,则比较次数越多。
最优情况下,二叉搜索树为完全二叉树,其平均比较次数为:logN
最差情况下,二叉搜索树退化为单支树,其平均比较次数为:N/2
🐭到这里我们就聊完了二叉搜索树,如果你有什么不懂或者其他见解欢迎在下方评论或者私信博主,也可以希望多多支持一下博主!!!🥰🥰,我们下一个篇章聊一聊java中的map和set😸