2-3树

2-3树
为了保证查找树的平衡性,我们需要一些灵活性,因此在这里我们允许树的一个结点保存多个键。我们将一颗二叉查找树的结点称为2-结点(包含一个键和两条链接),现在我们引入3-结点(包含两个键和三条链接)。

一颗2-3查找树或为一颗空树,或由以下结点组成:
  • 2-结点,含有一个键和两条链接,左链接指向的2-3树中的键都小于该结点,有链接都大于。
  • 3-结点,含有两个键和三条链接,中链接指向的2-3树中的键都位于该结点的两个键之间。

一颗完美平衡的2-3查找树中的所有空链接到根节点的距离都应该是相同的。

查找
将二叉查找树的查找一般化我们就能得到2-3树的查找算法。
要判断一个键是否在树中,我们先将他和根节点中的键比较。如果它和其中任意一个相等,查找命中;否则我们根据比较的结果找到指向相应区间的链接,并在其指向的子树中递归地查找。如果这是个空链接,查找未命中。

插入
1.向2-结点中插入新键
要在2-3树中插入一个新结点,我们可以和二叉查找树一样先进行一次未命中的查找,然后把新结点挂在树的底部,但这样树无法保持完美平衡性。我们使用2-3树的主要原因在于它能够在插入后继续保持平衡。
如果未命中的查找结束于一个2-结点,我们只要把这个2-结点替换为一个3-结点,将要插入的键保存在其中即可。

2.向一颗只含3-结点的树中插入新建
在考虑一般情况之前,先假设我们需要向一颗只含有3-结点的书中插入一个新键。这棵树有两个键,所以在它唯一的结点中已经没有可以插入新键的空间了。为了将新键插入,我们临时将新键存入该结点中,使之成为一个4-结点。它很自然地扩展了以前的结点并含有3个键和4条链接。创建一个4-结点很方便,因为很容易将它转换为一颗3个2-结点组成的2-3树。
【(A=E)】    —>   【(A=E=S)】   —>     【(A)】 <= 【(E)】 => 【(S)】

3.向一个父节点为2-结点的3-结点中插入新键
假设未命中的查找结束于一个3-结点,而它的父节点是一个2-结点。在这种情况下我们需要在维持树的完美平衡的前提下为新键腾出空间。
我们像之前一样构造一个临时的4-结点并将其分解,但此时我们不会为中键创建一个新结点,而是将其移动至原来的父节点中。剩下的结点分解为两个2-结点。

4.向一个父节点为3-结点的3-结点插入新键
如果从插入结点到根节点的路径上全都是3-结点,我们的根结点最终变成一个临时的4-结点。此时我们可以按照向一颗只有一个3-结点的树中插入新键的方法处理这个问题。我们将临时的4-结点分解为两个3-结点,使树高加1。

5.分解根节点
如果从插入结点到根节点的路径上全都是3-结点,我们的根节点最后会变成一个临时的4-结点。此时我们可以按照向一颗只有一个3-结点的树中插入新键的方法处理这个问题。我们将临时的4-结点分解为3个2-结点,使树高加1。这次变换仍然保持了树的完美平衡型。

2-3在最坏情况下也有着很好的性能。升序插入中,二叉查找树会得到一颗高度为9的最差查找树,而使用2-3树,树高为2。

含有10亿个结点的一颗2-3树的高度仅在19到30之间。我们最多只需要访问30个结点就能够在10亿个键中进行任意的查找和插入操作。
但是,2-3树的真正实现还有一段距离,尽管我们可以用不同的数据类型表示2-结点和3-结点并写入变换所需的代码,但这种直白的表示方式实现大多数的操作并不方便,因为需要处理的情况实在太多。我们需要维护两种不同类型的结点,它产生的额外开能会使算法比标准的二叉查找树更慢。平衡一棵树的初衷是为了消除最坏的情况,但我们希望这种保障所需的代码越少越好。我们将用红黑二叉查找树的简单数据结构来表达并实现它。 

猜你喜欢

转载自blog.csdn.net/chen45682kang/article/details/81060766
2-3