【问题标题】:Difference in performance between the following two implementations of binary search以下两种二分查找实现的性能差异
【发布时间】:2021-02-16 17:30:17
【问题描述】:

我在《Competitive Programmer's Handbook》https://cses.fi/book/book.pdf一书中遇到了这两种二分搜索的实现。

方法一:


int a = 0, b = n-1; 

while (a <= b) { 
    int k = (a+b)/2; 
    if (array[k] == x) { 
        // x found at index k
    }
    if (array[k] > x) 
        b = k-1; 
    else
        a = k+1;
}

方法二:

int k = 0; 

for (int b = n/2; b >= 1; b /= 2){ 
    while (k+b < n && array[k+b] <= x) 
        k += b;
}
if (array[k] == x){ 
    // x found at index k
}

我猜方法 2 并不完全是二分搜索。 我知道 method 1method 2 都有 O(log n) 复杂度。此外,方法 2 的代码更简单,因此可能会导致更少的错误。

我的问题是:

  • 使用方法 2 时性能是否有任何提升?
  • 方法 2 还有其他优势吗?

【问题讨论】:

  • 方法 2 不是搜索的有效实现。将它与搜索的实现进行比较是没有任何意义的。你也可以比较二分查找和无操作。 no-op 比二分查找快,好的,接下来呢?
  • 更少的代码和“更简单”(我不同意)并不意味着更少的错误。我认为第一个实现更容易理解,我可以立即看到发生了什么,所以我更喜欢这个实现。
  • 方法2有效,几乎可以称为二分查找,但比看起来要复杂得多。
  • 方法 2 绝对不简单(在实践中可能更慢),尽管它包含一个可以在二进制搜索之外应用的巧妙想法。
  • @YvesDaoust 当 n 为奇数时,将测试数组索引 a[k+b] 和 a[k+2b](在最坏的情况下)。 while 确实需要是 while 并且不能是 if。这就是为什么我认为代码具有欺骗性。

标签: algorithm performance binary-search coding-efficiency


【解决方案1】:

对于这么短的代码和这么少的差异,不可能做任何预测。时间性能将取决于编译器如何优化,还取决于数组中键的分布(特别是命中而不是未命中的概率)。

我不同意所有“反对”第二种方法的 cmets(即使在完全正确的情况下声称它有问题)。它基于一个可能使它变得更好的原则:循环体中只有一个测试。

比较相等性(方法 1)给人一种错误的感觉,即算法将在找到密钥时提前终止并加快搜索速度*。但事实并非如此,因为对于一半的键,决策树的完整深度无论如何都是必要的,而且这并不能因为有两个比较而不是一个而抵消。

*事实上,您平均只需 一个 测试!


只有基准测试才能告诉您其中一种方法在特定测试用例中是否更快。我敢打赌,运行时间的分布重叠很多。 (不算,几乎不可能以一种能代表其在实际环境中的行为的方式对如此快速的算法进行基准测试。)


最后评论:方法2是二元搜索,而实际上方法1是三元

【讨论】:

  • 如果我们有一个原语来执行b=clear_all_bits_but_most_significant(n),那么while可以转换成if,让方法2更加高效。
  • @aka.nice:现代处理器确实有条件移动指令,它不会承受条件分支的沉重成本。优化器很可能会在这里使用它们。
  • 重新。你最后的评论。对于方法 1,您可以将 equal 比较移到循环下方,以使两者在这方面等效,
  • 这是对语义的争论,也许并不重要,但方法 2 可以称为“三元”,因为当 n 为奇数时,数组被分成三部分:[0, n/2), [n/ 2, n-1) 和 [n-1, n)。 “拆分”是指这三个范围中的一个将在下一次迭代中下降。
  • 对于第二个实现,只有一个比较似乎不正确。首先检查 k + b
猜你喜欢
  • 2011-09-25
  • 1970-01-01
  • 2020-09-15
  • 1970-01-01
  • 2011-03-30
  • 1970-01-01
  • 1970-01-01
  • 2019-10-02
  • 2023-01-25
相关资源
最近更新 更多