【问题标题】:Efficient implementation Binary Search高效实现二分查找
【发布时间】:2018-04-13 12:18:42
【问题描述】:

我有一个关于实现二进制搜索的算法测试,该测试最多可在 2 秒内工作。

首先,我实现了一个递归版本的二分搜索,但在某些测试用例中它需要将近 3.6 秒才能完成。然后,我将其更改为迭代版本,但在同一个测试用例中需要 2.6 秒。但是,我认为使用while loop 是需要花费大量时间的一个原因。

我的问题是:我需要改进什么才能使其最多花费 2 秒?

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int iterBinarySearch(vector<int> A, int low, int high, int key) {
    int mid;
    while (low <= high) {
        mid = low + ((high - low)/2);
        if (key < A[mid]) {
            high = mid -1;
        } else if (key > A[mid]) {
            low = mid +1;
        } else {
            return mid;
        }
    }
    return -1;
}

int main() {

    vector<int>dict;
    vector<int>keys;

    int dictSize;
    cin >> dictSize;
    while (dictSize--) {
        int val;
        cin >> val;
        dict.push_back(val);
    }

    int keysSize;
    cin >> keysSize;
    while (keysSize--) {
        int val;
        cin >> val;
        keys.push_back(val);
    }

    sort(dict.begin(), dict.end());
    int size = (int)dict.size() -1;
    for(int i = 0; i< keys.size(); ++i) {
        if ((dict[0] > keys[i]) || (dict[size] < keys[i])) {
            cout << "-1" << ' ';
        } else {
            int res = iterBinarySearch(dict, 0, size, keys[i]);
            cout << res << ' ';
        }
    }
    return 0;
}

【问题讨论】:

  • 尝试两件事:开启优化,不要按值取值vector(取值const&amp;
  • 阅读dictSizekeysSize 后,请立即致电vector::reserve()
  • 你为什么不用binary_search
  • @MEnnabah - 不,他说得很清楚const &amp;
  • @MEnnabah - 不,您需要修改函数参数。 vector&lt;int&gt; const &amp;A。它将绑定到dict 就好了(无需更改任何其他内容)。在const-ness 的假设下,优化编译器可以创造奇迹。

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


【解决方案1】:

只有两件事是明显浪费的:

  1. int iterBinarySearch(vector&lt;int&gt; A, int low, int high, int key) 复制向量(可能包含来自您的评论的 100,000 个元素),而

    int iterBinarySearch(const vector&lt;int&gt; &amp;A, int low, int high, int key)(或任何其他 const-ref 拼写)将直接搜索您的原始向量,无需复制

  2. 当您事先知道大小时,您对字典和键向量的初始push_back 是浪费的:因为您没有告诉向量它将有多大,它必须不断调整大小和复制。只需添加

        cin >> dictSize;
        dict.reserve(dictSize); // grow to the correct size just once
        while (dictSize--) {
          int val;
          cin >> val;
          dict.push_back(val);
        }
    

    键也一样。

现在,除了跳出的这两件事之外,理想情况下,您应该尝试分析您的代码,而不是仅仅猜测缓慢的位置。

【讨论】:

  • @MEnnabah 您是否重试了递归方法?它应该与矢量参考一起正常工作;)
  • @grek40 实际上,不,我没有重试递归方法。但是,如果它在所有测试用例的相同持续时间内工作,那就太好了,我会考虑重试它并检查它所花费的时间。
【解决方案2】:

1.主要问题是当您将 dict 参数作为值传递时。

只需将其作为 const 引用传递即可。

int iterBinarySearch(const vector<int> &A, int low, int high, int key) {
    // your code 
}

2。也尝试更改此行

mid = low + ((high - low)/2);

mid = (low + high)/2;

注意:仅当矢量大小不大于 INT_MAX / 2 时才进行第二次更改。

【讨论】:

  • 有时有助于防止溢出。
  • 我明白,但如果他关心性能,那会有所帮助。
  • 在处理性能时,不要靠猜测来优化。 mid 的计算不太可能成为主要瓶颈。
  • @Gor 第二点不是正确的做法,因为我要更改lowhigh 的值,我需要移动mid。如果我这样做了,它总是会用整个向量大小计算mid,这是不正确的。
  • 更改mid的计算有什么意义?如果这是为了性能,那么根本不清楚为什么您的版本应该更快
【解决方案3】:

如前所述,将向量作为常量引用传递是一个重点,使用reserve 另一个。完全不分配密钥也可以为您提供一些进一步的性能:

sort(dict.begin(), dict.end());

int keysSize;
cin >> keysSize;

// this is a constant loop constraint, so move it out, too...
int size = (int)dict.size() - 1;

while (keysSize--)
{
    int val;
    cin >> val;

    if (val < dict[0] || val > dict[size])
    {
        cout << "-1" << ' ';
    }
    else
    {
        int res = iterBinarySearch(dict, 0, size, keys[i]);
        cout << res << ' ';
    }
}
return 0;

您可以保护一个额外的函数调用:

cout << "-1 ";

当然,不会给你太多,但这太简单了,所以我还是提一下……


附带说明:当处理本质上不能为负的值时(大小、数组索引等),我更喜欢带符号数据类型的无符号计数器部分(在您的情况下为 unsigned int)。这根本不会对性能产生任何影响,就像现代二进制补码架构一样,将使用完全相同的操作(也许除了一些比较之外),只是更清楚地显示了变量的意图和(部分)有效范围立即从数据类型(需要提及的一个例外:假设您需要 int64_t 进行签名,但可以使用 uint32_t,并且您有 32 位架构,例如微控制器 - 那么你真的得到一些最小的性能提升...)。

【讨论】:

  • 我害怕在这种情况下使用unsigned,但是在您对帖子发表评论之后,我相信我需要重新考虑。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-05
  • 2014-05-10
  • 2016-02-23
  • 1970-01-01
  • 2011-09-25
相关资源
最近更新 更多