一尘不染

如何在O(n)时间内对双向链表进行二进制搜索?

algorithm

我听说有可能在O(n)时间内对双链表实现二进制搜索。访问双向链表中的随机元素需要O(n)的时间,而二进制搜索访问O(log
n)个不同的元素,那么运行时间不应该是O(n log n)吗?


阅读 225

收藏
2020-07-28

共1个答案

一尘不染

说双向链接列表上的二进制搜索的运行时间为O(n log
n),从技术上来说是正确的,但这并不是一个严格的上限。使用更好的二进制搜索实现和更巧妙的分析,可以使二进制搜索在时间O(n)上运行。

二进制搜索的基本思想如下:

  • 如果列表为空,则搜索的元素不存在。
  • 除此以外:
    • 看中间的元素。
    • 如果它与所讨论的元素匹配,则将其返回。
    • 如果它大于所讨论的元素,则丢弃列表的后半部分。
    • 如果它小于所讨论的元素,则丢弃列表的前半部分。

在双链表上执行二进制搜索的简单方法是通过计算索引以查找每次迭代(就像在数组中一样),然后通过从列表的开头开始并向前扫描适当的索引来访问每个索引。步骤数。这确实很慢。如果要搜索的元素位于数组的最后,则查找的索引将为n
/ 2、3n / 4、7n / 8等。总结最坏情况下的工作,我们得到

n / 2 + 3n / 4 + 7n / 8 + 15n / 16 + …(Θ(log n)项)

≥n / 2 + n / 2 + … + n / 2(Θ(log n)项)

=Θ(n log n)

n / 2 + 3n / 4 + 7n / 8 + 15n / 16 + …(Θ(log n)项)

≤n + n + … + n(Θ(log n)项)

=Θ(n log n)

因此,该算法的最坏情况时间复杂度为Θ(n log n)。

但是,通过更智能的方法,可以将其加快Θ(log
n)。先前算法速度慢的原因是,每次我们需要查找元素时,我们都从数组的开头开始搜索。但是,我们不需要这样做。第一次查找中间元素之后,我们已经在数组的中间,并且我们知道我们将要进行的下一次查找将位于n
/ 4或3n / 4位置,这仅是距离我们离开的地方的距离n / 4(如果从数组的开头开始,则为n / 4或3n / 4)。如果我们只是从停止位置(n /
2)迁出到下一个位置,而不是从列表的开头重新开始怎么办?

这是我们的新算法。首先扫描到数组的中间,这需要n / 2个步骤。然后,确定是访问数组前半部分中间还是数组后半部分的元素。从位置n / 2到达那里仅需要n /
4个总步数。从那里开始,到包含该元素的数组的四分之一的中点仅需n / 8步,而从那里到包含该元素的数组的八分之一的中点仅需n /
16步,依此类推。所执行的步骤总数为

n / 2 + n / 4 + n / 8 + n / 16 + …

= n(1/2 + 1/4 + 1/8 + …)

≤n

这是由于无限几何级数1/2 + 1/4 + 1/8 + …的和为1。因此,在最坏的情况下完成的总功只有Θ(n),比以前的Θ(n log
n)最坏情况更好。

最后一个细节: 为什么要这么做?
毕竟,已经花费O(n)时间在双向链表中搜索元素。这种方法的一个主要优点是,即使运行时为O(n),我们最终也只能进行O(log
n)个总比较(二进制搜索的每一步一个)。这意味着,如果比较昂贵,使用二元搜索可能会比普通的线性搜索完成更少的工作,因为O(n)来自遍历列表的工作而不是进行比较的工作。

2020-07-28