本篇博客主要基于这篇博客的扩展,建议阅读前先阅读这篇博文,这篇博文详细介绍二叉搜索树的实现原理,完整代码在github的
binary.go
文件中
引言 #
之所以使用Go来实现,个人还是比较喜欢Go的,作为一个基础数据结构,Go用来实现这个速度比Java、C++都快,而且相比Java也能节省内存,而且我也不喜欢换使用冒号。
二叉搜索树的基本功能 #
作为一个树结构,主要必须实现三个功能,查增删(当然有改,不过改是删和增的结合所以就不算在里面),当然二叉树还能帮我实现一些额外功能,比如寻找最大值最小值等等,实现一颗二叉搜索树必须要保证在“增”和“删”的时候保持树结构不变,也就是保证还是一颗二叉搜索树
什么二叉树是二叉搜索树呢,二叉搜索树只需要保证一点
- 任何一个节点,它的左子树(不为空)所有值小于这个节点值,它的右子树(不为空)所有值大于这个节点值
所有我们在实现增删的时候必须要牢记这点,而且我们也可以知道在你开始对这颗二叉树进行增删的时候,它一定已经满足了上面这个条件
查 #
如何在上面这颗标准的二叉树里面查询到我们想要的值呢?我们知道一颗二叉树只需要知道根节点(上面的10)就能通过遍历得到所以的节点的值,所以我们在实现中声明一个二叉搜索树的时候只保存一个root
节点,通过这个节点,我们就可以实现所以的增删查改功能了
为了找到每个值a,我们依次要在每个节点上对值进行比较,因为每个节点的值代表已经将这堆数据分成两个部分,一堆比这个值大在右边,一堆比这个值小在左边,这样只要最终能找到一个那个值的最小区间的对应值(找到)或者找到一个为空的叶节点(代表没有找到),
通过最简单的查找,我们可以反推出一个理论,假如我们需要插入一个值,我们只要先尝试去寻找到那个值,假如找到了,因为已经存贮了就不插入,假如没找到就会找到一个为空的叶子节点,那样只要在父节点将这个为空的叶子节点替换成这个值为a的节点,我们就完成了插入
所以查找和插入是相对的,知道怎么查找就知道怎么插入,接下来我们详细介绍如何删除,这个非常关键,因为它是后面要介绍的高级树实现高效的删除的关键
删除 #
我们还是以上面那种图举例子,删除我们最先想到的是删除12,17那些节点,直接删除掉对二叉树没有什么影响,但是如果我们要删除那些带有节点的呢?我们先简化问题,先看一个只有两个节点的树
我们要想删除10,有两种可能
一种是将左边值移过来,一种是将右边值移过来,这两种都不会破坏二叉树的平衡,现在我们假设我们倾向将左边的值移过来,我们再回到上面那棵标准的二叉树,如果我们要删除掉10节点,我们要把那个值移过来,我们直接在可视化界面进行这个操作
我们发现,我们只需要将7和放到10的位置上就完成了一次删除的操作,而7与10的关系是,7是10左边的那堆值最大的,用一个通俗的话来说,完成改朝换代的关键就是要找一个能镇的住手下的人,由于“10”挂了,在左边只有“7”能镇住左边,所以"7”升官直接跑到“10”的位置了
根据这个原则我们将情况分成三种
- 左右子树都为空,直接删掉
- 左边子树或者右边子树有一个为空,将不为空的子树提上来当做删除的节点
- 左右子树不为空,将左边最大的值提上来替换删除的节点
对于第三种情况,为了编程方便,我们考虑一种情况,如果左子树的右节点有没有值(比如说上面的5节点),如果它右节点有值,那么左边的最大值肯定出现在,左子树的右节点上(就像上面左子树出现在5的右节点的7节点上面)。然后我们继续判断“7”节点有没有右子树,这样循环下去,我们总能找到一个节点没有右节点,这样我们就找到左子树的最大值了。
在上面这种情况下,我们将这情况分成两种
- 如果左子树的右节点有值(循环下去找到一个节点右节点没有值)
- 如果左子树的右节点没值(那么第一个左子树就是最大值)
具体的代码在binary.go
的Delete
(寻找节点)方法和delete
(删除节点)方法中
总结 #
总的来说,二叉搜索树的插入是最容易理解的,但是删除的话要考虑不同四种的情况所以还是稍微需要点时间理解一下的,小小的一个二叉搜索树也花了近200行代码实现,但是相比后面的AVL树、红黑树的近千行代码来说,二叉搜索树还是比较简单的,而且后面实现的高级树结构基本上都是依赖二叉搜索树的实现(必须要按照二叉树的增删满足二叉搜索树的原则),所以我们对二叉搜索树的增删必须理解透彻。
引用: