二进制搜索的性能比线性搜索差

时间:2019-09-08 17:52:27

标签: arrays algorithm time-complexity binary-search

我正在leetcode.com上练习基本的编程技能(我强烈推荐它。这很棒),并且遇到了有趣的结果。

我试图找到给定数组A第一个不动点,该点具有严格的升序值。

我以两种方式做到这一点。首先,二进制搜索/线性时间混合

    start, end = 0, len(A) - 1
    while(start <= end):
        middle = (start + end) / 2
        if A[middle] == middle:
            for i in range(start, middle + 1):
                if A[i] == i:
                    return i
        elif A[middle] > middle:
            end = middle - 1
        elif A[middle] < middle:
            start = middle + 1
    return -1  

这是简单的二进制搜索,直到找到匹配项,然后依次遍历所有可能的第一个固定点,直到找到第一个匹配项。这是线性部分进入的地方。例如,假设len(A) = 1001A[500] = 500是唯一的固定点,那么我在二分查找的一次迭代中找到了它,但是随后我必须从索引开始0至500,一个接一个,寻找第一个匹配项。那是n/2或换句话说O(n)

第二种解决方案是纯二进制搜索

    start, end = 0, len(A) - 1
    fixed_point = -1
    while(start <= end):
        middle = (start + end) / 2
        if A[middle] == middle:
            fixed_point = middle
            end = middle - 1
        elif A[middle] > middle:
            end = middle - 1
        elif A[middle] < middle:
            start = middle + 1
    return fixed_point     

我希望它会更好,但实际上会更糟。

第一个解决方案在运行时间方面击败了所有提交的97%,运行时间为40毫秒。

第二种解决方案仅击败所有提交的72%,运行时间为48毫秒。

我很好奇为什么会这样。第二种解决方案真的更好吗?

1 个答案:

答案 0 :(得分:2)

A[middle] == middle处:“找到固定点的任何索引”的位置。第一个版本更有效地找到中点运行的起点,假设它发生在线性扫描的起点“ fsvo”附近(fsvo):每个二进制循环的成本“更昂贵”( C),而不考虑所需的循环数(O复杂度)。 对于特定的简并输入,应该有可能构造纯二进制搜索更好的情况: n的大小和输入的分布。

例如,[0, 0, 0 ..., midpoint=1, 1, 1 ...]的简并情况在第一个版本中将是O(n),因为线性循环将超过start=0..midpoint=n/2 1 使用此退化数据以及足够大的n值,纯二进制搜索将在边界更好的情况下占主导地位。但是,假定它是“分布良好的随机数据”,则采用线性方法探针可以在非常小的线性扫描集中进行磨练(对于最终循环,C较小)。

这类似于为什么线性扫描对于小型阵列来说更快,以及为什么合并排序或快速排序(例如)可以切换到插入排序以进行近叶排序的原因。 Big-O将行为描述为n-> Inifinity,而没有考虑固定成本。

1 扫描也可能是midpoint=n/2..start=0。在这种情况下,所示的简并情况是理想情况,而相应的简并情况将是[0, 1, 1, 1 ...]