高级树结构

导语:总结所有的树结构

1 哈希树

哈希树(hash tree, Merkle tree),在密码学及计算机科学中是一种树形数据结构,每个叶节点均以数据块的哈希作为标签,而除了叶节点以外的节点则以其子节点标签的加密哈希作为标签。哈希树能够高效、安全地验证大型数据结构的内容,是哈希链的推广形式。

哈希树的概念由瑞夫*墨客于1979年申请专利,故亦称墨克树(Merkle tree)。

概念:哈希树中,哈希值的求取通常使用诸如SHA-2的加密哈希函数,但如果只是用于防止非故意的数据破坏,也可以使用不安全的校验和获取,比如CRC。

哈希树的顶部为顶部哈希(top hash),亦称根哈希(root hash)或主哈希(master hash)。以从P2P网络下载文件为例:通常先从可信的来源获取顶部哈希,如朋友告知、网站分享等。得到顶部哈希后,则整棵哈希树就可以通过P2P网络中的非受信来源获取。下载得到哈希树后,即可根据可信的顶部哈希对其进行较验,验证数据是否完整,是否遭到破坏。

2 二叉树

在计算机科学中,二叉树是每个节点最多只有两个分支(即不存在分支度大于2的节点)的树结构。通常分支被称作“左子树”或“右子树”。二叉树的分支具有左右次序,不能随意颠倒。

二叉树的第i层至多拥有$2^{i-1}$个节点;深度为k的二叉树至多共有$2^{k+1}-1$个节点(定义根节点所在深度k0=0),而总计拥有节点数符合的,称为“满二叉树”;深度为k的n个节点的二叉树,当且仅当其中的每一节点,都可以和同样深度k的满二叉树,序号为1到n的节点一对一对应,称为完全二叉树。对任何一颗非空的二叉树T,如果其叶片(终端节点)数为n0,分支度为2的节点数为n2,则n0=n2+1。

与普通树不同,普通树的节点个数至少为1,而二叉树的节点个数可以为0;普通树节点的最大分支没有限制,而二叉树节点的最大分支度为2;普通树的节点无左、右次序之分,而二叉树的节点有左、右次序之分。

二叉树通常作为数据结构应用,典型用法是对节点定义一个标记函数,将一些值与每个节点相关系。这样标记的二叉树就可以实现二叉搜索树二叉堆

3 红黑树

是一种自平衡二叉查找树,是计算机科学中用到的一种数据结构,典型用途是实现关联数组。它在1972年由鲁道夫*贝尔发明,被称为“对称二叉B树”,它现代的名字源于1978发表的一篇论文。红黑树的结构复杂,但它的操作有着良好的最坏情况运行时间,并且在实践中高效:它可以在$O(logn)$时间内完成查找、插入和删除,这里的n是树中元素的数目。

红黑树是每个节点都带有颜色属性的二叉查找树,颜色为红色或者黑色。在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:

  1. 节点是红色或者黑色。
  2. 根是黑色。
  3. 所有叶子都是黑色。
  4. 每个红色节点必须有两个黑色的子节点。(从每个叶子到根的所有路径不能有连续的红色节点。)
  5. 从任一节点到其每个叶子的所有简单路径都包含相同数量的黑色节点。

这些约束确保了红黑树的关键特性:从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果是这个树大致上是平衡的。因为操作比如插入、删除和查找某个值的最坏情况时间都要求与树的高度成比例,这个高度上的理论上限允许红黑树在最坏的情况下都是高效的。

最短的可能路径都是黑色节点,最长的可能路径有交替的红色或黑色节点。

用途和好处

4 B树

在计算机科学中,B树是一种自平衡的树,能够保持数据有序。这种数据结构能够让查找数据、顺序访问、插入数据及删除的动作,都在对数时间内完成。B树,概括来说是一个一般化的二叉查找树一个节点可以拥有2个以上的子节点。与自平衡二叉查找树不同,B树适用于读写相对大的数据块的存储系统,例如磁盘。B树减少定位记录时所经历的中间过程,从而加快存取速度。B树这种数据结构可以用来描述外部存储。这种数据结构常被应用在数据库和文件系统的实现上。

在B树中,内部(非叶子)节点可以拥有可变数量的子节点(数量范围预先定义好)。当数据被插入或从一个节点中移除,它的子节点数量发生变化。为了维持在预先设定的数量范围内,内部节点可能会被合并或者分离。因为子节点数量有一定的允许范围,所以B树不需要像其他自平衡查找树那样频繁地重新保持平衡,但是由于节点没有被完全填充,可能浪费了一些空间。子节点数量的上界和下界依特定的实现而设置。例如,在一个2-3 B树(通常简称2-3树),每一个内部节点只能有2或3个子节点。

B树中每一个内部节点会包含一定数量的键,键将节点的子树分开。例如,如果一个内部节点有3个子节点(子树),那么它就必须有两个键: a1 和 a2 。左边子树的所有值都必须小于 a1 ,中间子树的所有值都必须在 a1 和a2 之间,右边子树的所有值都必须大于 a2 。

通常,键的数量被选定在d和2d之间。其中d是键的最小数量,d+1是树最小的度或分支因子。在实际中,键值占用了节点中大部分的空间。因数2将保证节点可以被拆分或组合。如果一个内部节点有 2d 个键,那么要添加一个键值给此节点,只需要拆分这 {\displaystyle 2d+1} 个键为2个 拥有 d 个键的节点,并把中间值节点移动到父节点。每一个拆分的节点需要拥有足够数目的键。相似地,如果一个内部节点和他的邻居两者都有 {\displaystyle d}d 个键,那么将通过它与邻居的合并来删除一个键。删除此键将导致此节点拥有 {\displaystyle d-1}{\displaystyle d-1} 个键;与邻居的合并则加上 {\displaystyle d}d 个键,再加上从邻居节点的父节点移来的一个键值。结果为完全填充的 {\displaystyle 2d}2d 个键。

一个节点的分支(或子节点)的数量会比存储在节点内部键值的数量大1。在 2-3 B树中,内部节点将会存储1个键值(带有2个子节点)或2个键值(带有3个子节点)。一个B树有时会被描述为 {\displaystyle (d+1)-(2d+1)}{\displaystyle (d+1)-(2d+1)} 或简单地使用最高分支 {\displaystyle (2d+1)}{\displaystyle (2d+1)}

一个B树通过约束所有叶子节点在相同深度来保持平衡。深度在元素添加至树的过程中缓慢增长,而整体深度极少地增长,并导致所有叶子节点与根节点距离加1。

在存取节点数据所耗时间远超过处理节点数据所耗时间的情况下,B树在可选的实现中拥有很多优势,因为存取节点的开销被分摊到里层节点的多次操作上。这通常出现在当节点存储在二级存储器如硬盘存储器上。通过最大化内部里层节点的子节点的数量,树的高度减小,存取节点的开销被缩减。另外,重新平衡树的动作也更少出现。子节点的最大数量取决于,每个子节点必需存储的信息量,和完整磁盘块的大小或者二次存储器中类似的容量。虽然 2-3 树更易于解释,实际运用中,B树使用二级存储器,需要大量数目的子节点来提升效率。

变体:

B+树:这些键值的拷贝被存储在内部节点:键值和记录存储在叶子节点;另外,一个叶子节点可以包含一个指针,指向另一个叶子节点以加快顺序存取。

B*树:分支出更多的内部邻居节点以保持内部节点更密集地填充。此变体要求非根节点至少2/3填充,而不是1/2.为了维持这样的结构,当一个节点填满之后将不会再立即分割节点,而是将它的键值与下一个节点共享。当两个节点都填满之后,分割成3个节点。

计数B树存储:每一树都带有一个指针和其指向子树的节点数目。这就允许了以键值为序快速查找第N笔记录,或者统计2笔记录之间的记录述目,还有其他很多相关的操作。

5 AVL树

AVL树是计算机科学中最早被发明的自平衡二叉查找树。在AVL树中,任一节点对应的两颗子树的最大高度差为1,因此它也被称为高度平衡树。查找、插入和删除在平均和最坏情况下的时间复杂度都是$O(log n)$。增加和删除元素的操作在可能需要借由一次或多次树旋转,以实现树的重新平衡。AVL树得名于它的发明者在1962年的论文《An algorithm for the organization of information》中公开了这一数据结构。

节点的平衡因子是它的左子树的高度减去它的右子树的高度(有时相反)。带有平衡因子1、0、-1的节点被认为是平衡的。带有平衡因子-2或2的节点被认为是不平衡的,并且需要重新平衡这个树。平衡因子可以直接存储在每个节点中,或从可能存储在节点中的子树高度计算出来。

6 伸展树

是一种自我平衡的二叉查找树,它能在均摊$O(log n)$的时间内完成基于伸展(splay)操作的插入、查找、修改和删除操作。是在1985年发明的。

在伸展树上的一般操作都基于伸展操作:假设想要对一个二叉查找树执行一系列的查找操作,为了使整个查找时间更小,被查频高的那些条目就应当经常处于靠近树根的位置。于是想到设计一个简单的方法,在每次查找之后对数进行调整,把被查找的条目搬移到离树根近一些的地方。伸展树应运而生。伸展树是一种自我调整形式的二叉查找树,它会沿着从某个节点到树根之间的路径,通过一系列的旋转把这个节点搬移到树根去。

它的优势在于不需要记录用于平衡树的冗余信息。

优点

伸展树的自我平衡使其拥有良好的性能,因为频繁访问的节点会被移动到更靠近根节点,进而获得更快的访问速度。(用于数据库设计过程中,利用相似的思想设计一个计算器,每次查询自增,查询时首先计数器大的开始查询)

缺点

伸展树最显著的缺点是它有可能会变成一条链。例如,在以非递减顺序访问全部n个之后就会出现这种情况。此时树的高度对应于最坏情况的时间效率,操作的实际时间效率可能很低。然而均摊的最坏情况是对数级的$O(log n)$

即使以”只读”方式(例如通过查找操作)访问伸展树,其结构也可能会发生变化。这使得伸展树在多线程环境下会变得很复杂。具体而言,如果允许多个线程同时执行查找操作,则需要额外的维护和操作。这也使得它们不适合在纯粹的函数式编程中普遍使用,尽管用于实现优先级队列的方式不多。

7 散列表

散列表,也叫哈希表,是根据键直接访问在内存存储位置的数据结构。也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查询速度。这个映射函数称作散列函数,存放记录的数组称作散列表。

基本概念:

Table of Contents