高效实施二进制搜索

时间:2017-11-01 07:58:37

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

我有一个关于实现二进制搜索的算法测试,最长时间为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;
}

3 个答案:

答案 0 :(得分:3)

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

  1. int iterBinarySearch(vector<int> A, int low, int high, int key)复制矢量(可能会有评论中的100,000个元素),而

    int iterBinarySearch(const vector<int> &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);
        }
    

    和钥匙相同。

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

答案 1 :(得分: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时才进行第二次更改。

答案 2 :(得分:1)

如前所述,将向量作为const引用传递是一个重点,使用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位架构,例如微控制器 - 然后你真的得到一些最小的性能提升......)。