【问题标题】:Linear search vs binary search efficiency线性搜索与二分搜索效率
【发布时间】:2019-12-20 07:27:26
【问题描述】:

我目前正在研究不同的搜索算法,我做了一个小程序来看看效率上的差异。二分搜索应该比线性搜索快,但时间测量显示不同。我在代码中犯了一些错误还是这是一些特殊情况?

#include <chrono>
#include <unistd.h>

using namespace std;

const int n=1001;
int a[n];

void assign() {
    for (int i=0; i<n; i++) {
        a[i]=i;
    }
}

void print() {
    for (int i=0; i<n; i++) {
        cout << a[i] << endl;
    }
}

bool find1 (int x) {
    for (int i=0; i<n; i++) {
        if (x==a[i]){
            return true;
        } 
    } return false;
}

bool binsearch(int x) {
    int l=0,m; 
    int r=n-1;
    while (l<=r) {
        m = ((l+r)/2);
        if (a[m]==x) return true;
        if (a[m]<x) l=m+1;
        if (a[m]>x) r=m-1;

    }
    return false;
}

int main() {

    assign();
    //print();
    auto start1 = chrono::steady_clock::now();
    cout << binsearch(500) << endl;
    auto end1 = chrono::steady_clock::now();

    auto start2 = chrono::steady_clock::now();
    cout << find1(500) << endl;
    auto end2 = chrono::steady_clock::now();
    cout << "binsearch: " << chrono::duration_cast<chrono::nanoseconds>(end1 - start1).count()
        << " ns " << endl;
    cout << "linsearch: " << chrono::duration_cast<chrono::nanoseconds>(end2 - start2).count()
        << " ns " << endl;

    return 0;
}

【问题讨论】:

  • TL;博士。由于内存局部性,在足够小的子集上,二分搜索可能比线性搜索慢。
  • 试试 n=10 百万..
  • 您是否记得在启用优化的情况下编译您的代码?
  • 您忘记了所有这些复杂性声明的开头语句:“有一个n_0,因此对于所有n &gt; n_0 ...”。
  • 请注意,if (a[m]&gt;x) 是不必要的。你已经知道它不是平等的,也不是更少的。所以它必须更大。将该行更改为else r=m-1;

标签: c++ algorithm performance search binary-search


【解决方案1】:

您的测试数据集太小(1001 个整数)。当您填充它时,它将完全适合最快的 (L1) 缓存;因此,您受到分支复杂性的约束,而不是内存。 二进制搜索版本表现出更多的分支错误预测,导致比简单的线性传递更多的管道停顿。

我将n 增加到1000001 并且还增加了测试通过次数:

auto start1 = chrono::steady_clock::now();
for (int i = 0; i < n; i += n/13) {
    if (!binsearch(i%n)) {
        std::cerr << i << std::endl;
    }
}
auto end1 = chrono::steady_clock::now();

auto start2 = chrono::steady_clock::now();
for (int i = 0; i < n; i += n / 13) {
    if (!find1(i%n)) {
        std::cerr << i << std::endl;
    }
}
auto end2 = chrono::steady_clock::now();

我得到了不同的结果:

binsearch: 10300 ns
linsearch: 3129600 ns

另请注意,您不应该在定时循环中调用cout,但您确实需要使用查找的结果以使其不会被优化。

【讨论】:

    【解决方案2】:

    在我看来 N=1001 足以说明二分查找的性能更好。 Specific realizations of linear search 仅对小 N(大约

    如果你只是交换他们的电话,你会得到相反的结果:

    binsearch: 6019 ns 
    linsearch: 77587 ns 
    

    对于精确测量,您应该使用特殊框架(例如google benchmark),这可以确保两种算法的“公平条件”。

    其他在线benchmarking tool(它在负载未知的许多 AWS 机器的池上运行测试代码并返回平均结果)为您的代码提供这些图表而无需更改(同样 n=1001):

    【讨论】:

    • 重用缓存数据是有意义的。我没有考虑过。还有一件事我不明白。我选择了 n=1001 并且出于一个原因搜索 500。在 binsearch 函数中,m 将等于 500,这应该只导致两次比较 - 一个用于 while 循环,第一个 if 将返回 true,而在 linsearch 中,“for 循环”将进行 1000 次比较(i
    • 但是如果首先启动 binsearch,它必须在“a[m]==x”行读取未缓存的数据,这比几次比较的成本要高得多。无论如何,这是一个依赖于硬件/编译器的问题,而不是精确的分析。平均而言,在众多函数调用中,您的见解似乎完全正确,只需看一下图表:binsearch 立即返回答案。
    【解决方案3】:

    两全其美!

    在一定程度上进行二分搜索,然后切换到线性。这样想,二分查找有一堆记账;线性搜索更快,因为它“更简单”。

    当我第一次用汇编语言尝试这个(早在 1970 年代)时,我推断将二进制搜索减少到大约 4 个项目,然后进行线性搜索,这是最佳的。然而YMMV;这取决于硬件、比较两个项目的复杂性(浮点数/整数/字符串/其他)等。

    提示:计算代码中的操作数。我发现 binsearch() 例程中的每个步骤所需的操作量大约是线性扫描的两倍。

    【讨论】:

      猜你喜欢
      • 2012-05-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-12
      • 2016-01-19
      • 2014-11-05
      • 2019-01-09
      • 2010-10-16
      相关资源
      最近更新 更多