二叉树的前世今生

注:以下内容纯属本人yy,按照个人的理解,梳理下来,如果有不对的地方请指正。

为什么要有树

一百个数字,无序排列,我们要根据已知的某个数字a,找到a在这一百个数字中的位置(也就是下标),如何寻找?遍历。假设100个数字可以每次遍历,1万个可以每次遍历,100万个还可以么?再计算遍历的频率,不好好掌握,简单的遍历就可以把负责遍历的人给忙死。

这时候人们开始思考,怎么样可以快速定位呢?很简单,如果插入的时候有顺序,那么这样的遍历就变得非常简单。我们都知道1-100中间如果要找某个数字,我们肯定第一个判断的是50这个数值与要定位的数字a的大小对比(因为有序了,所以大小对比才有意义)。如果小于50,那么我们下一次会先判断25,如果大于50,那么我们会判断75……这就是常见的二分法,我们判断出来要找的数字小于50时,我们就不需要去判断50-100中间的这一堆数字,对比遍历,我们就相当于少了一半多工作量,一直循环下去,对比遍历,遍历的数据越多,效率越高。具体研究请自行百度 二分法 时间/空间复杂度,去得出结论。


编程中遇到这样的问题就有更多,因为查找的效率严重影响我们系统反应的时间。计算机中如何体现上文所述的二分法呢?聪明的人根据二分法的判断顺序,设计了一个有左子树,右子树的树形结构,命名为二叉树。二叉树必须有序,否则无序的二叉树,数据来了都不知道要放在什么地方

比如我们设计一个树,排序规则为,大于节点的放在左边,小于节点的放在右边
二叉树的前世今生
得出这样一个树,有序,但这就是二叉树的最坏情况,不能做到高效查询,如果我们没有定义好一个最有意义的ROOT节点,那么很可能会出现这种情况,那么情况总是不能避免,到底如何定义一个最有意义的ROOT节点呢?于是,平衡左树与右树的平衡二叉树应运而生。(它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树)

二叉树旋转的实现 左旋/右旋
1) 右旋: 左边树结构深度 大于 右边树结构深度,且大于1时,进行右旋
二叉树的前世今生
2) 左旋: 左边树结构深度 小于 右边树结构深度,且小于1时,进行左旋
二叉树的前世今生


将平衡二叉树完成之后,有效的数据排序之后,大致会得到一个类似下图的结果。
二叉树的前世今生

这样的情况下,我们进行1-7的数字判断,最快需要1次,最慢需要3次,而使用传统的遍历对比,最快要1次,最慢7次。数据量越大的情况下,这里的性能差距会越大。


平衡二叉树诞生,让所有人尝到了甜头,其实简单的说就是,就是降低了二叉树的时间复杂度,即从最顶端,到最底端的时间。那么二叉树的层数到底还能不能减?当然可以,于是,2-3树诞生。

2-3树每个节点保存1个或者两个的值。对于普通的2节点(2-node),他保存1个key和左右两个自己点。对应3节点(3-node),保存两个Key,2-3查找树的定义如下:

1) 要么为空,要么:

2) 对于2节点,该节点保存一个key及对应value,以及两个指向左右节点的节点,左节点也是一个2-3节点,所有的值都比key有效,有节点也是一个2-3节点,所有的值比key要大。

3)对于3节点,该节点保存两个key及对应value,以及三个指向左中右的节点。左节点也是一个2-3节点,所有的值均比两个key中的最小的key还要小;中间节点也是一个2-3节点,中间节点的key值在两个跟节点key值之间;右节点也是一个2-3节点,节点的所有key值比两个key中的最大的key还要大。

即类似这样的一个形状
二叉树的前世今生

这样的实现,如图中八个节点,在平衡二叉树中,需要深度为3的树结构,只需要两层就可以实现。但是对比平衡二叉树,大大增加了插入/删除/修改的难度,降低了查询的难度,利弊参半。

由于2-3树的复杂,人们考虑可不可以对2-3树和平衡二叉树的复杂度折中一下,于是出现了红黑树。
红黑树相当于一个2-3树的简单实现,简单演示一下2-3树如何转换为红黑树

2-3树
二叉树的前世今生

红黑树
二叉树的前世今生

当然这个树并不满足真正的红黑树,这里只做一个2-3树转换红黑树的过程,简单说一下对比2-3树,为什么更为广泛应用的原因:
2-3查找树,这种数据结构在插入之后能够进行自平衡操作,降低了时间复杂度。但是2-3查找树实现起来比较困难,红黑树是2-3树的一种简单高效的实现,运用颜色标记来替代2-3树中比较难处理的3节点问题。


本文只是个人理解,简单入门,如果需要仔细学习,建议阅读《算法导论》,我自己还没来得及看,都是自己的一些积累,有空一定仔细阅读一遍。另外下一片重点说明在数据量更多,更大的情况下,二叉树无法完全胜任的情况下,b-树和b+树衍变。