高效实现二分查找

Efficient implementation 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;
}

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;

NOTE: Make second change only if your vector size is not bigger than INT_MAX / 2.

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

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

  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. 当您事先知道大小时,您对 dict 和键向量的初始 push_back 是浪费:因为您没有告诉向量它将有多大,它有继续调整大小和复制。只需添加

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

    按键也一样。

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