【问题标题】:Why isn't my implementation O(NlogN)?为什么我的实现不是 O(NlogN)?
【发布时间】:2015-03-16 09:13:57
【问题描述】:

我正在实施和测试this SO question 的答案 -

给定一个整数数组,找出数组中所有有序元素对的数量,其和位于给定范围 [a,b]

The answer with the most upvotes(目前)仅提供算法的文本描述,应该是 O(NlogN):

对数组排序... 对于数组中的每个元素 x: 考虑元素之后的数组切片。 在这个数组切片上进行二分搜索[a - x],称之为y0。如果未找到完全匹配,则将大于 [a - x] 的最接近的匹配视为 y0。 只要 x + y

我的实现:

import bisect
def ani(arr, a, b):
    # Sort the array (say in increasing order).
    arr.sort()
    count = 0
    for ndx, x in enumerate(arr):
        # Consider the array slice after the element
        after = arr[ndx+1:]
        # Do a binary search on this array slice for [a - x], call it y0
        lower = a - x
        y0 = bisect.bisect_left(after, lower)
        # If you only need to count the number of pairs
        # Modify the ... algorithm so [b - x] ... is also searched for
        upper = b - x
        y1 = bisect.bisect_right(after, upper)
        count += y1 - y0
    return count

当我绘制时间与 N 或 N 的某个函数时,我看到的是指数或 N^2 响应。

# generate timings
T = list()    # run-times
N = range(100, 10001, 100)    # N
arr = [random.randint(-10, 10) for _ in xrange(1000000)]
print 'start'
start = time.time()
for n in N:
    arr1 = arr[:n]
    t = Timer('ani(arr1, 5, 16)', 'from __main__ import arr1, ani')
    timing_loops = 100
    T.append(t.timeit(timing_loops) / timing_loops)

是我的实现不正确还是作者的说法不正确?

这里是一些数据图。

T 与 N T / NlogN vs N - 一位评论者认为这不应该产生线性图 - 但确实如此。 T vs NlogN - 如果复杂度是 NlogN,我认为这应该是线性的,但事实并非如此。

【问题讨论】:

  • 你为什么要切片? bisect 函数采用下限和上限,因此您可以完全避免创建一个新列表(该列表累积需要 2N 次操作,乘以 N 步,使您的代码为 N^2,尽管内部循环使用快速 C 代码)。跨度>
  • @MartijnPieters 不幸的是,为了清楚起见,我试图保持简单,然后看不到该决定的后果。
  • 还有一个更紧凑的实现,使用sum()来说明:gist.github.com/mjpieters/88c039207be8353e8620

标签: python algorithm


【解决方案1】:

如果没有别的,这是你的错误:

for ndx, x in enumerate(arr):
    # Consider the array slice after the element
    after = arr[ndx+1:]

arr[ndx+1:] 创建长度为len(arr) - ndx 的列表的副本,因此您的循环为 O(n^2)。

改为将lohi 参数用于bisect.bisect

【讨论】:

  • 我不确定我是否遵循建议。您是说只搜索整个数组而不是其中的一部分吗?你也认为我实现了作者的意图吗?
  • 否,请参阅链接文档。 lohi 参数恰好用于仅搜索列表的一部分,但不需要显式创建该部分。
  • 是的,看来您确实实现了作者的意图。
  • @wwii:不,不是创建一个新列表after,而是使用ndx + 1作为bisect.bisect_left()函数的下限;第三个论点。将bisect.bisect_left(after, lower) 替换为bisect.bisect_left(arr, lower, ndx + 1) 以在索引ndx + 1 处开始二分。
  • 非常感谢。只见树木不见森林。
猜你喜欢
  • 1970-01-01
  • 2023-02-21
  • 1970-01-01
  • 2019-07-10
  • 1970-01-01
  • 2020-01-10
  • 1970-01-01
  • 2021-10-22
  • 1970-01-01
相关资源
最近更新 更多