我将说明如何做到这一点,但不会给你代码。
你从一个列表开始:
1 3 2 4 5 1 2 3 7
这里添加了职位,所以我们可以更轻松地讨论它。
1 3 2 4 5 1 2 3 7
0 1 2 3 4 5 6 7 8
我们想要的是一个新的“下一个最大元素的位置”数组,如下所示:
1 3 2 4 5 1 2 3 7
0 1 2 3 4 5 6 7 8
1 3 3 4 8 6 7 8 x
最后一个数组包含构建所需树所需的所有信息。挑战在于如何有效地生成该数组。
我们将通过从数组的后面向后走来生成它。在每个步骤中回答O(log(n)) 中的必要问题。我将描述的数据结构是Skip List 的一个变体。它会告诉我们,在按值排序的搜索中,可以找到大于该值的任何东西的最低索引。我们所做的是在时间O(log(n)) 中搜索我们的值,然后插入可以在时间O(log(n)) 中找到该值的事实。
如那篇文章所述,跳过列表是数据的链接列表。然后是一个较高的数据链表,总结了较低数据的一些内容。与“跳过”。然后是一个更高的链表。每个数据点都达到一个高度。一半的点位于高度 0(仅底部列表),一半位于高度 1,四分之一位于高度 2,依此类推。整个数据结构最终大小为n + n/2 + n/4 + ...,大小约为2n。所有操作平均在log(n) 个操作中完成。
我将在没有必要的指针的情况下绘制它来遍历数据结构,每一层都是一行。通过right/left/down/up 可以在视觉上执行的操作需要程序中的指针。我们的实际节点总是看起来像(value, first index met or exceeded)。 (您会看到节点值可能是陈旧的,但这没关系。)
现在,在跳过列表中,您通常会随机分配节点的高度。平均而言,这是可行的。但我将使用 2 的最高幂,将值除以我的身高。对于本示例,这实现了良好的高度分布,而没有令人困惑的随机选择。
诀窍在于,当我们读取跳过列表时,我们从顶部开始,然后向右移动,然后向下移动,直到找到大于或等于我们正在寻找的值。然后我们向左和向下移动(向左是该值仍然大于我们想要的值,否则向下)并沿着我们找到的第二条路径获取最小索引。当我们插入时,我们总是尝试向上移动,然后向左移动,沿着这条轨迹记录新插入的索引。
现在为了使操作的踪迹清晰,我将在我的数据结构中添加在此操作期间采用的路径。这意味着以下操作:
-
l正在寻找更高的值来开始搜索下一个索引。
-
s寻找下一个索引。 (我一找到这个就切换,所以如果我首先看到的是search 的一部分,可能根本就没有l。)
-
inserting 一个新节点(因为这是一个二维链表结构,你也有我没有显示的邻居指针的更新)。这发生在最后一个s 之前,然后直接上升。但前提是价值对我们来说是新的。
-
updating 关于节点的一些事情。它们从最后一个search 或insert 的左侧开始,然后尝试向上移动然后向左移动。如果这是一个新的最小值,则不会更新。
现在请记住,我们从以下开始:
1 3 2 4 5 1 2 3 7
0 1 2 3 4 5 6 7 8
我们想要在我们的跳过列表中的第一个事实是值7 可以在位置8 找到。这个事实的高度为 0,所以我们的跳过列表如下所示:
next: [x]
skip: (7,8)i
接下来我们发现3 可以在7 位置找到。搜索下一个最高值是微不足道的。我们在级别 0 插入另一个事实并得到以下结果:
next: [8,x]
skip: (3,7)i (7,8)s
接下来可以在位置6 找到值2。这引入了一个新的水平。
next: [6,7,8,x]
skip: (2,6)i
(2,6)i (3,7)s (7,8)
接下来可以在位置5找到值1。
next: [5,6,7,8,x]
skip: (2,6)s
(1,5)i (2,6)s (3,7) (7,8)
现在值5 出现在位置4。由于这是第一个复杂的,字母表明我们 looked 在顶部的 (2,6) 处开始搜索,然后向下,对,对。然后我们s只搜索(7,8)。然后我们insert 和下面的update 向左、向左、向上移动。请注意,左下角的陈旧(1,5) 永远不会给我们一个错误的结论,因为任何命中它的搜索都会首先找到(2,4),因此我们知道使用4 的索引而不是陈旧的5 .
next: [8,5,6,7,8,x]
skip: (2,4)lu
(1,5) (2,4)lu (3,4)lu (5,4)i (7,8)s
现在4 可以在3 位置找到。这次的阅读往下,左,左找到起始(5,4)。我们的插入返回一个然后直接向上移动以创建一个新的水平。我们有很多没有接触过的陈旧数据。
next: [4,8,5,6,7,8,x]
skip: (4,3)i
(2,4)l (4,3)i
(1,5) (2,4)l (3,4)l (4,3)i (5,4)s (7,8)
接下来,2 可以在位置2 找到。这些记录存在,所以这是update。
next: [3,4,8,5,6,7,8,x]
skip: (4,3)s
(2,2)u (4,3)s
(1,5) (2,2)u (3,4)s (4,3)s (5,4) (7,8)
我们快到了! 3 现在可以在位置1 找到。我们的搜索直接进行了。然后我们找到根索引,更新它然后向左,向上。
next: [3,3,4,8,5,6,7,8,x]
skip: (4,3)s
(2,1)u (4,3)s
(1,5) (2,1)u (3,1)i (4,3)s (5,4) (7,8)
现在我们向下、向左、向下、向左搜索最后一条记录。为了完整起见,我将更新数据结构以包含它,尽管我们永远不会再看。
next: [1,3,3,4,8,5,6,7,8,x]
skip: (4,3)s
(2,1) (4,3)s
(1,0)u (2,1)s (3,1)s (4,3)s (5,4) (7,8)
并且跳过列表的最终状态表示至少1的第一个值在0,至少2或3的第一个值在1,第一个值在至少4 在3,至少5 的第一个值在4。并且至少7 的第一个值位于8。