目录
一:要解决的问题,出发点
1.演进
树的定义:
树的深度(高度)
平衡二叉树(AVL树)
红黑树:
B树:
深夜有感,灵感乍现,忽然感觉对这个数据结构终于有了一个自己的理解,才感叹这些计算机先驱们的智慧,在此,记录个人对数据结构--树,的理解。按照事情发展的来龙去脉,从需求出发去一步一步演进理解,而不是像课本那样死板地给出定义和规则让人死记硬背,才能更好契合地理解。
这是计算机存储的技术,准确地说,应该是信息存储的技术,总的来说,目的都在于:怎么使用最少的数据量,存储最多的信息。 存储的大量信息,怎么以最少的访问次数,最快地找到想要的数据。 前面的问题,比如编码,已经有了霍夫曼编码,其基本的思想是,对于出现次数最多的相同元素,使用最短的编码信息去表示。 这样总的编码用到的信息是最少的。
后面的问题,对于一堆数据,怎么存储(这里指的是在内存中存储,运行时程序动态使用的数据,而不是存盘,存盘的话那就是数据库了),才能在查找访问的时候,最快呢?
最快访问,从数据的角度来看就是遍历的数据量最少。 我们围绕着这个问题,来逐一解析,红黑树这种东西是怎么发展出来的。
首先,一堆数据,必须要有一个访问的入口,从实现的角度出发,最好是设计简单(后续也会围绕简单化这个思想考虑),最好的方式就是使用唯一的一个入口,考虑最简单的设计,就是把这些数据用一条线串联起来,这就是最简单的链式结构,链表。
但是这样的效率是不高的,他只有一条路径,要找到最后一个数据,就需要遍历访问前面所有的数据。 这样的时间复杂度 O(n).
如果从入口出发,设计多条路径呢:
但是这里我们发现有个问题,如果一个数据节点有两个入线(被箭头指向)的话,像上图中的1,2,3 编号节点,有两条入线,就难免回发生像节点3这样,橙色的入线其实是在走逆向,从A节点到3号节点这个路径相当于往靠近根节点的方向在走回头路,我们希望从中间往外走路径越短越好,当然不希望走回头路,那样肯定是走了更多的距离。
为了避免这样的问题,可以限制节点,只有一条入线。按照书本的定义就是有唯一前驱,而后继不唯一。(这其实就是图和树的区别,树是一对多(一个入线多个出),图是多对多(不限),树关注解决的是从根节点访问到某一个节点路径的长短,图关注解决的任意两个节点的连通性)
拿掉所有类似上面的橙色线条,让所有节点只有唯一的入线(唯一前驱)
唯一前驱,这样倒推回去的话,每一个节点从根节点访问过来的路基都是唯一的只有一条,问题就变得更简化了,我们不需要考虑一个节点到底有几种访问路径,只有一条。所以只要知道这个节点的深度(所在的层次),就是这个节点访问时的路径长度。
为了好看,把个别分支都转动到底部来,形成一种层次结构,开起来就是一颗树了:
一颗树:
还不够,进一步简化,从入口节点出来的有多个分支,其实只需要两个分支是最简单地,就好像计算机是二进制的,只需要两种状态经过多重的组合就可以表示更多的信息,这也是简化基础的设计。
所以再简化,因为基本是从上到下的层次,干脆把箭头也省略成简单的直线,从上到下就是指向,就是我们的 二叉树。
一颗二叉树,我们从入口(根节点)出发,访问到最远的节点(叶子节点),走过的节点的个数--路径长度 可以称为这个 时间复杂度,我们要求它越小越好,这个数据比较重要,我们用一个词来表述,深度,树的深度(或者高度),就是距离根节点最远的那个节点的路径长度,(描述性能,所以记录导致性能最差的这个边界值,用最长的那个作为这个树的深度)
回到之前的问题,这些数据怎么组织存放,让查找数据的路径尽可能小。
可以换一个角度想,固定长度的一个路径(深度固定),怎么让存储的数据量最多。
现在以二叉树的方式存储,怎们在固定路径长度的情况下存储最多的数据,当然是尽可能把它填满,这就是满二叉树。 一颗深度为k的二叉树,最多(也就是填满的情况下), 可以存数据:等比数列求和,高中数学,a1(1-q^n)/(1-q) => 也就是2^k-1个数据。
反过来,要在n个数构成的满二叉树中查找数据,时间复杂度(也就是访问的路径长度,最短访问长度0,最长访问长度及树的深度) log 以2为底n的对数,一般直接写着时间复杂度为log(n) 没有说明底数,其实就是二叉树就是底为2,三叉树底为3。平时用的多的,基本是二叉树。 这就是二叉树查找访问的时间复杂度的由来。
AVL树得名于它的发明者G. M. Adelson-Velsky和E. M. Landis
当然,数据个数不可能都是2^n-1个,填不满的,称为“ 完全二叉树”。 而实际上的问题是,数据往往不能保证是一层一层地按照顺序往下填的,如下图:
这样访问 2号节点的路径长度为6,树的深度成了6. 换个角度看,就是深度为6的树,其实最大可以存储 2^6-1个节点的,而现在,只存储了15个数据,浪费了很多空间。 要避免这中存储情况的发生,应该尽量让二叉树是按照从上到下,填满上层再填下一层,而不希望出现一层没填满,就在下一层填,那样的话,整颗树看起来就像一边数据多,一边数据少,”不平衡“。 照这个逻辑,我们引入平衡二叉树:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。 反过来讲,如果子树的高度差操作了1,说明高度大的那颗子树的最后一层节点,是应该被放到上一层来填充,左右高度是不平衡的。
最优二叉搜索树(也称霍夫曼树)。。。。。
所以数据尽量按照平衡二叉树来存储,不过呢,平衡二叉树其实是一个理想值,这属于理论,而实际应用起来,还需要考虑其他因素,(就像三体里面汪淼的应用物理和丁仪的理论物理)。 实际使用过程中,对于一个平衡二叉树,进行插入和删除操作,势必破坏树的平衡性质,要维护平衡,就需要再做额外的修改,这就是 左旋 右旋等等这些操作,通过修改部分节点的出入线路径来达到平衡。而这些操作带来的消耗也不少,结果是严格的平衡二叉树,查找性能是最优了,但是插入删除后维持平衡性质却带来了额外的操作开销导致总的操作开销还是上去了。 那么有没有办法来平衡查找和插入删除总的操作开销呢?
红黑树就是为了这个目的而来,它实际上属于半平衡的特性,并不是完全平衡,但是降低了插入删除操作带来的额外消耗,在总的性能中实现了一个折中,虽然它的查找操作不是最理想的平衡二叉树的性能,但是插入和删除操作的消耗降低了不少,综合性能上更加“平衡”。
关于平衡二叉树的插入删除后维护平衡到底带来了多少的额外开销?而红黑树又是如何降低这个开销的?有待进一步探讨.......??????????????????
红黑树性质:
性质1:每个节点要么是黑色,要么是红色。
性质2:根节点是黑色。
性质3:每个叶子节点(NIL)是黑色。
性质4:每个红色节点的两个子节点一定都是黑色。 不能有两个红色节点相连。
性质5:任意一节点到每个叶子节点的路径都包含数量相同的黑结点。
同样,考虑到实际使用过程中,数据量过大,并不能一次都读到内存中去,有可能在程序访问数据的时候需要和磁盘打交道,而磁盘的读写操作慢,应该尽量减少操作,B树就是专门为了应对这种情况而设计的特殊平衡树。 一般用于数据库。就是把关键信息存放在节点,这个节点放到内存中,它可以由多个子节点,所有这些子节点在磁盘中。 通常以一整页数的据作为这个节点的所有字节点,方便一次性以页为单位读写。
算法导论18章B树拍图:
这就是二叉树,平衡二叉树(AVL树),红黑树,发展进化的过程,个人理解。