二分搜索树
定义
二分搜索树(Binary Search Tree),也称二叉查找树,有序二叉树,排序二叉树,是指一棵空树或者具有下列性质的二叉树:
- 若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 任意节点的左、右子树也分别为二叉查找树。
没有键值相等的节点(no duplicate nodes)。
如下图就是一个二分搜索树,其本质就是一个二叉树,并且不一定是满的,而且元素要可比较大小。

优势
二分搜索树在某些方面是具有优势的,比如我们从时间复杂度上来分析一下:

实现
二分搜索树的实现
简单的实现一下,通过一个私有类实现包含其所需的节点,值,左节点,右节点。
1 | public class BinarySearchTree<E extends Comparable<E>> { |
向树中添加元素
其实插入元素使用递归的方式很容易,其核心思想:从根节点开始找插入的位置,满足二叉搜索树的特性,比左子节点大,比右子节点小.
具体的步骤:
从根节点开始,先比较当前节点,如果当前节点为null那么很明显就应该插入到这个节点。
如果上面的节点不是null,那么和当前节点比较,如果小于节点就往左子树放,如果大于节点就往右子树放。
然后分别对左子树或者右子树递归的递归进行如上1、2步骤的操作
此时就用到了递归,那么递归是对某一个问题,它的子问题需要是同样的模型。此处的一个小的问题就是:对某个node,然后进行操作,所以参数应该有个node才能实现循环起来。此处向以node为根的二叉搜索树中,插入节点value.
1 | /** |
注意
在这里抠一下关于递归添加元素这个函数为啥要返回插入新节点后二分搜索树的根,

比方说上面这棵树,我要添加元素12,执行递归的第一步之后,node节点其实是28这个根节点,然后执行
add(node.left, e);
其实就是像下面的树中添加元素,但是现在的节点还是28,执行完之后就是下面这棵树,返回以16为根节点的这棵树,然后赋值给node.left。其实就是相当于把这棵树连接到了28这个节点上。最后返回node也就是28这个节点,其实就是新添加元素之后的树。

查询树中包含某一个元素
其实查询包含树中是否包含某个元素与向树中添加一个元素的思路是类似的,都是通过递归的方式查询左右子树。
1 | /** |
树的遍历
其实树的遍历和图的遍历是一样的,分为两种,一种是广度优先遍历,一种是深度优先遍历。
深度优先遍历
深度优先遍历又分为三种:
前序遍历(Preorder Traversal):先访问当前节点,再依次递归访问左右子树
中序遍历(Inorder Traversal):先递归访问左子树,再访问自身,再递归访问右子树
后序遍历(Postorder Traversal):先递归访问左右子树,最后再访问当前节点。
比如一棵树:

前序遍历的结果是:28–16–13–22–30–29–42
中序遍历的结果是:13–16–22–28–30–29–42
后序遍历的结果是:13–22–16–29–42–30–28
前序遍历的递归实现
递归实现的思想是非常简单的,从根节点开始,判断根节点是否有值,有则输出,然后递归的去处理器左子树,最后去处理其右子树。
1 | /** |
前序遍历的非递归实现
关于前序遍历的非递归实现这里使用栈的方式,具体的思路如下:

具体的实现其实就是从根节点开始向堆栈中压入元素,然后弹出元素,然将该根节点的左右孩子按顺序压入堆栈,然后弹出栈顶元素,在将弹出的元素的左右孩子按顺序压入堆栈,不断的执行,直到树中没有元素,堆栈中就只弹出元素,直至堆栈为空。
具体的实现如下:
1 | /** |
中序遍历的递归实现
1 | /** |
后序遍历的递归实现
1 | /** |
广度优先遍历(层序遍历)
层序遍历的实现从根节点开,一次安深度往下遍历,按照先左孩子然后右孩子的方式遍历,这里使用队列的方式来实现层序遍历,具体的实现方式如下:

1 | /** |
删除书中的节点
删除树中的最大元素和最小元素
首先是先要找到最大和最小元素,根据二分搜索树的性质我们可以很容易的就找到最大最小的元素
1 | /** |
找到了最大个最小元素之后要将其删除,有需要注意的地方就是当树的结构如下时:

从图上我们可以看出,要是删除的最小元素还有右孩子或者要删除的最大元素还有左孩子的时候,就需要重新把待删除元素的根节点指向其孩子节点。
具体的实现如下:
1 | /** |
删除树中任意节点
删除树中任意的节点需要考虑的问题就多一些了
首先删除的节点只有左孩子或者只有右孩子,这个时候其实是比较简单的,删除该节点后,其子节点有其父节点继承即可。如下:


但是当要删除的节点既有左孩子又有右孩子的时候就需要注意了,这里使用Hibbard Deletion方法来实现,具体如下:

其实核心的思想即使找到该节点中右子树中的最小节点来替代该节点成为新的节点。
具体的实现如下:
1 | /** |
注意
这里删除的操作为什么需要返回删除元素之后的新的树的根节点其实和添加元素的道理是一样的,可以参照一下。一样的理解方式。
这样就完成了树的简单的实现
拓展
当然树还有很多的东西可以去设计,比方说把每个树的节点代表的含义设置的更加丰富
如:树节点除了存值,还存储以其为根节点的树的元素的个数,也就是树的size

或者是存储该节点在树中的深度

或者是把树设计成可以存储重复的元素,就是在该节点在存一个代表其元素个数的值。

总之树的拓展还有很多,待慢慢探索实现。
