通过与冒泡排序配对来加速链表的典型 "find"?
Speed up typical "find" of linked list by pairing with bubble sort?
我正在制作一种类似第四语言的语言,它应该 运行 在内存非常宝贵的地方。
由于 space 限制和缺少可调整大小的内存,该语言使用链表作为语言单词词典。
但是,一想到链表查找的性能开销,我就心痛。我知道最坏的情况永远是 O(n),但是当我意识到一些事情时,我正试图想办法至少改进典型情况:如果“查找”方法除了找到密钥之外,还每次按下一个键时执行一个类似冒泡排序的操作。这样,最常用的键就会“冒泡”到顶部。更好的是,随着编译的继续,整个列表将被重新加权,并且应该与键的连续统计可能性大致相关。
这个技术在其他地方用过吗?我很好奇是否有一个体面的数学证明它的 运行 时间复杂度(假设一些名字与其他名字的统计曲线)。单个冒泡排序操作显然是 O(1),因此至少它不会损害理论上的 运行 时间复杂度。
虽然此策略在常见情况下应该会提高平均值 run-time,但这不会改变 worst-case 的复杂性,即 O(n)
。
确实,如果搜索到的关键字位于大小为 n
的列表的末尾,则将在 O(n)
时间内找到 运行。 bubble-swap 操作 运行s 在 O(1)
时间内(假设密钥实际上可以在常数时间内进行比较)。如果获取相同的键但仍然 O(n)
,则下一个查找操作会更快一些。在 n
次提取同一个密钥后,可以在 O(1)
时间内完成提取此密钥。但是,以特定顺序获取 n
其他键可以重新排序列表,以便将初始键放在末尾。更具体地说,获取初始键旁边的项目 n
次就可以了。最后,以特定顺序获取 n
次初始密钥和 n
其他密钥会导致 (n + n-1 + n-2 + ... + 2 + 1) + (1 + 2 + ... + n-1 + n) = n*(n-1) = n²-n = O(n²)
操作。在这些操作之后,列表应该处于与初始状态相同的状态。因此,复杂度显然是 O(n)
.
请注意,您可以实施缓存来加快提取速度。存在许多政策。您可以找到其中一些描述 here。请注意,这不会影响复杂性,但肯定会大大缩短执行时间。缓存可以存储一个迭代器到链表的一个节点。当项目为 inserted/deleted 时,迭代器不会失效(除非目标项目实际被删除)。
另请注意,链表通常非常慢。它们在内存使用方面也不是很有效,因为指向下一项的指针需要一些 space(在 64 位架构上为 8 个字节)。分配的节点也可能需要一些隐藏的 space 关于所使用的标准库分配器(一些存储指针元数据,如分配的 space)。使用较少内存的解决方案是使用包含 key-value 对的链表。
请注意,虽然 平衡二叉搜索树 需要更多内存,但它们可以更有效地解决此问题。在这种数据结构中找到一个键的复杂度是 O(log n)
。一个好的 hash-map 实现在内存中也可以非常紧凑(参见 hopscotch hashing),尽管 hash-map 的内存消耗在调整大小时可能会非常大。
我正在制作一种类似第四语言的语言,它应该 运行 在内存非常宝贵的地方。
由于 space 限制和缺少可调整大小的内存,该语言使用链表作为语言单词词典。
但是,一想到链表查找的性能开销,我就心痛。我知道最坏的情况永远是 O(n),但是当我意识到一些事情时,我正试图想办法至少改进典型情况:如果“查找”方法除了找到密钥之外,还每次按下一个键时执行一个类似冒泡排序的操作。这样,最常用的键就会“冒泡”到顶部。更好的是,随着编译的继续,整个列表将被重新加权,并且应该与键的连续统计可能性大致相关。
这个技术在其他地方用过吗?我很好奇是否有一个体面的数学证明它的 运行 时间复杂度(假设一些名字与其他名字的统计曲线)。单个冒泡排序操作显然是 O(1),因此至少它不会损害理论上的 运行 时间复杂度。
虽然此策略在常见情况下应该会提高平均值 run-time,但这不会改变 worst-case 的复杂性,即 O(n)
。
确实,如果搜索到的关键字位于大小为 n
的列表的末尾,则将在 O(n)
时间内找到 运行。 bubble-swap 操作 运行s 在 O(1)
时间内(假设密钥实际上可以在常数时间内进行比较)。如果获取相同的键但仍然 O(n)
,则下一个查找操作会更快一些。在 n
次提取同一个密钥后,可以在 O(1)
时间内完成提取此密钥。但是,以特定顺序获取 n
其他键可以重新排序列表,以便将初始键放在末尾。更具体地说,获取初始键旁边的项目 n
次就可以了。最后,以特定顺序获取 n
次初始密钥和 n
其他密钥会导致 (n + n-1 + n-2 + ... + 2 + 1) + (1 + 2 + ... + n-1 + n) = n*(n-1) = n²-n = O(n²)
操作。在这些操作之后,列表应该处于与初始状态相同的状态。因此,复杂度显然是 O(n)
.
请注意,您可以实施缓存来加快提取速度。存在许多政策。您可以找到其中一些描述 here。请注意,这不会影响复杂性,但肯定会大大缩短执行时间。缓存可以存储一个迭代器到链表的一个节点。当项目为 inserted/deleted 时,迭代器不会失效(除非目标项目实际被删除)。
另请注意,链表通常非常慢。它们在内存使用方面也不是很有效,因为指向下一项的指针需要一些 space(在 64 位架构上为 8 个字节)。分配的节点也可能需要一些隐藏的 space 关于所使用的标准库分配器(一些存储指针元数据,如分配的 space)。使用较少内存的解决方案是使用包含 key-value 对的链表。
请注意,虽然 平衡二叉搜索树 需要更多内存,但它们可以更有效地解决此问题。在这种数据结构中找到一个键的复杂度是 O(log n)
。一个好的 hash-map 实现在内存中也可以非常紧凑(参见 hopscotch hashing),尽管 hash-map 的内存消耗在调整大小时可能会非常大。