C ++:从带有

时间:2017-02-06 06:43:46

标签: c++ algorithm queue

任务是从双数组中提取k个最小元素及其索引,可能包括与k最小的元素绑定的更多元素。 E.g:

input: {3.3,1.1,6.5,4.2,1.1,3.3}
output (k=3): {1,1.1} {4,1.1} {0,3.3} {5,3.3} 

[这似乎是一项非常常见的任务,但我无法在SO上找到类似的线程 - 处理关系。希望我没有错过,也没有重复这个问题。]

我提出了以下解决方案,该解决方案有效并且似乎在复杂性方面相当有效。例如。随机1MLN双倍和k=10 MSVC 2013需要大约40ms。我想知道是否更好/更清洁/ 更高效(对于大数据和/或大k)< / em>执行此任务的方式(k值的验证和类似的事情是我们的范围)。避免使用所有元素分配队列?利用std::partial_sumstd::nth_element

typedef std::pair<double, int> idx_pair;
typedef std::priority_queue<idx_pair, std::vector<idx_pair>, std::greater<idx_pair>> idx_queue;

std::vector<idx_pair> getKSmallest(std::vector<double> const& data, int k)
{
    idx_queue q;
    {
        std::vector<idx_pair> idxPairs(data.size());
        for (auto i = 0; i < data.size(); i++)
            idxPairs[i] = idx_pair(data[i], i);

        q = idx_queue(std::begin(idxPairs), std::end(idxPairs));
    };

    std::vector<idx_pair> result;

    auto topPop = [&q, &result]()
    {
        result.push_back(q.top());
        q.pop();
    };

    for (auto i = 0; i < k; i++)
        topPop();

    auto const largest = result.back().first;
    while (q.empty() == false)
    {
        if (q.top().first == largest)
            topPop();
        else
            break;
    }

    return result;
}

工作示例是here

2 个答案:

答案 0 :(得分:1)

正如提问者所指出的,我建议首先复制double的向量并使用nth_element找出第k个元素。 然后进行线性扫描并获得小于或等于第k个元素的元素。时间复杂度应该是线性的。

但是,比较双倍时应该小心。

vector<idx_pair> getKSmallest(vector<double> const& data, int k){
    vector<double> data_copy = data;
    nth_element(data_copy.begin(), data_copy.begin() + k, data_copy.end());
    vector<idx_pair> result;
    double kth_element = data_copy[k - 1];
    for (int i = 0; i < data.size(); i++)
        if (data[i] <= kth_element)
            result.push_back({i, data[i]});
    return result;
}

更新:也可以通过维护最大为k的最大堆来找到kth_element。

在nth_element方法中,只需要O(k)内存用于堆而不是O(n)内存。

它需要O(n log k)时间,但如果k很小,那么我认为它应该与O(n)方法相当。 我不确定,但我的理由是堆可能被缓存,你不需要花时间复制数据。

vector<idx_pair> getKSmallest(vector<double> const& data, int k)
{
    priority_queue<double> pq;
    for (auto d : data){
        if (pq.size() >= k && pq.top() > d){
            pq.push(d)
            pq.pop();
        }
        else if (pq.size() < k)
            pq.push(d);
    }
    double kth_element = pq.top();
    vector<idx_pair> result;
    for (int i = 0; i < data.size(); i++)
    if (data[i] <= kth_element)
        result.push_back({i, data[i]});
    return result;
}

答案 1 :(得分:1)

这是@ piotrekg2建议的替代解决方案 - 使用nth_element平均O(N)复杂度:

bool equal(double value1, double value2)
{
    return value1 == value2 || std::abs(value2 - value1) <= std::numeric_limits<double>::epsilon();
}

std::vector<idx_pair> getNSmallest(std::vector<double> const& data, int n)
{
    std::vector<idx_pair> idxPairs(data.size());
    for (auto i = 0; i < data.size(); i++)
        idxPairs[i] = idx_pair(data[i], i);

    std::nth_element(std::begin(idxPairs), std::begin(idxPairs) + n, std::end(idxPairs));

    std::vector<idx_pair> result(std::begin(idxPairs), std::begin(idxPairs) + n);

    auto const largest = result.back().first;
    for (auto it = std::begin(idxPairs) + n; it != std::end(idxPairs); ++it)
        if (equal(it->first, largest))
            result.push_back(*it);

    return result;
}

实际上,代码看起来更清晰一些。但是,我已经运行了一些测试,根据经验,这个解决方案比std::priority_queue的原始解决方案略慢。

注意:Petar下面的答案使用std::nth_element提供了一个类似的解决方案,在我的实验中,这个解决方案比这个更好,并且比使用std::priority_queue的解决方案更好 - 可能是因为消除了在对上操作并使用原始双精度操作。