19

我需要在 C++ 中找到未排序、长度为 n、数组/向量的 k 个最大元素的索引,其中 k < n。我已经看到如何使用 nth_element() 来查找第 k 个统计信息,但我不确定使用它是否是解决我的问题的正确选择,因为我似乎需要对 nth_statistic 进行 k 次调用,我猜它会有 O(kn) 的复杂度,这可能会尽可能好?或者有没有办法在 O(n) 中做到这一点?

在没有 nth_element() 的情况下实现它似乎我必须遍历整个数组一次,在每一步填充最大元素的索引列表。

标准 C++ 库中是否有任何东西使它成为单行或任何聪明的方式来自己在几行中实现它?在我的特殊情况下,k = 3 和 n = 6,因此效率不是一个大问题,但如果找到一种干净且有效的方法来对任意 k 和 n 执行此操作,那就太好了。

看起来标记未排序数组的前 N ​​个元素可能是我可以在 SO 上找到的最接近的帖子,这些帖子在 Python 和 PHP 中。

4

7 回答 7

9

这是我的实现,它可以满足我的需求,并且我认为它相当有效:

#include <queue>
#include <vector>
// maxindices.cc
// compile with:
// g++ -std=c++11 maxindices.cc -o maxindices
int main()
{
  std::vector<double> test = {0.2, 1.0, 0.01, 3.0, 0.002, -1.0, -20};
  std::priority_queue<std::pair<double, int>> q;
  for (int i = 0; i < test.size(); ++i) {
    q.push(std::pair<double, int>(test[i], i));
  }
  int k = 3; // number of indices we need
  for (int i = 0; i < k; ++i) {
    int ki = q.top().second;
    std::cout << "index[" << i << "] = " << ki << std::endl;
    q.pop();
  }
}

这给出了输出:

index[0] = 3
index[1] = 1
index[2] = 0
于 2013-02-15T22:29:33.273 回答
9

这应该是 @hazelnusse 的改进版本,它被执行O(nlogk)而不是O(nlogn)

#include <queue>
#include <iostream>
#include <vector>
// maxindices.cc
// compile with:
// g++ -std=c++11 maxindices.cc -o maxindices
int main()
{
  std::vector<double> test = {2, 8, 7, 5, 9, 3, 6, 1, 10, 4};
  std::priority_queue< std::pair<double, int>, std::vector< std::pair<double, int> >, std::greater <std::pair<double, int> > > q;
    int k = 5; // number of indices we need
  for (int i = 0; i < test.size(); ++i) {
    if(q.size()<k)
        q.push(std::pair<double, int>(test[i], i));
    else if(q.top().first < test[i]){
        q.pop();
        q.push(std::pair<double, int>(test[i], i));
    }
  }
  k = q.size();
  std::vector<int> res(k);
  for (int i = 0; i < k; ++i) {
    res[k - i - 1] = q.top().second;
    q.pop();
  }
  for (int i = 0; i < k; ++i) {
    std::cout<< res[i] <<std::endl;
  }
}

8 4 1 2 6

于 2016-07-15T08:38:53.840 回答
7

这个问题有部分答案;也就是说,std::nth_element返回“第 n 个统计量”,其属性是第 n 个之前的元素都没有大于它,并且它后面的元素都没有小于它

因此,只需一次调用std::nth_element足以获得 k 个最大的元素。时间复杂度将为 O(n),理论上是最小的,因为您必须至少访问每个元素一次才能找到最小(或在本例中为 k 最小)的元素。如果您需要对这 k 个元素进行排序,那么您需要对它们进行排序,即 O(k log(k))。因此,总共 O(n + k log(k))。

于 2014-05-06T04:33:50.317 回答
3

您可以使用快速排序算法的基础来做您需要的事情,除了重新排序分区之外,您可以摆脱超出所需范围的条目。

它被称为“快速选择”,这是一个 C++ 实现

int partition(int* input, int p, int r)
{
    int pivot = input[r];

    while ( p < r )
    {
        while ( input[p] < pivot )
            p++;

        while ( input[r] > pivot )
            r--;

        if ( input[p] == input[r] )
            p++;
        else if ( p < r ) {
            int tmp = input[p];
            input[p] = input[r];
            input[r] = tmp;
        }
    }

    return r;
}

int quick_select(int* input, int p, int r, int k)
{
    if ( p == r ) return input[p];
    int j = partition(input, p, r);
    int length = j - p + 1;
    if ( length == k ) return input[j];
    else if ( k < length ) return quick_select(input, p, j - 1, k);
    else  return quick_select(input, j + 1, r, k - length);
}

int main()
{
    int A1[] = { 100, 400, 300, 500, 200 };
    cout << "1st order element " << quick_select(A1, 0, 4, 1) << endl;
    int A2[] = { 100, 400, 300, 500, 200 };
    cout << "2nd order element " << quick_select(A2, 0, 4, 2) << endl;
    int A3[] = { 100, 400, 300, 500, 200 };
    cout << "3rd order element " << quick_select(A3, 0, 4, 3) << endl;
    int A4[] = { 100, 400, 300, 500, 200 };
    cout << "4th order element " << quick_select(A4, 0, 4, 4) << endl;
    int A5[] = { 100, 400, 300, 500, 200 };
    cout << "5th order element " << quick_select(A5, 0, 4, 5) << endl;
}

输出:

1st order element 100
2nd order element 200
3rd order element 300
4th order element 400
5th order element 500

编辑

该特定实现具有 O(n) 平均运行时间;由于选择枢轴的方法,它共享快速排序的最坏情况运行时间。通过优化枢轴选择,您的最坏情况也变为 O(n)。

于 2013-02-15T20:39:46.023 回答
2

标准库不会为您提供索引列表(它旨在避免传递冗余数据)。但是,如果您对 n 个最大元素感兴趣,请使用某种分区(两者std::partition都是std::nth_elementO(n)):

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

struct Pred {
    Pred(int nth) : nth(nth) {};
    bool operator()(int k) { return k >= nth; }
    int nth;
};

int main() {

    int n = 4;
    std::vector<int> v = {5, 12, 27, 9, 4, 7, 2, 1, 8, 13, 1};

    // Moves the nth element to the nth from the end position.
    std::nth_element(v.begin(), v.end() - n, v.end());

    // Reorders the range, so that the first n elements would be >= nth.
    std::partition(v.begin(), v.end(), Pred(*(v.end() - n)));

    for (auto it = v.begin(); it != v.end(); ++it)
        std::cout << *it << " ";
    std::cout << "\n";

    return 0;
}
于 2013-02-15T22:06:22.217 回答
0

尽管以下代码可能无法满足所需的复杂性约束,但它可能是前面提到的优先级队列的有趣替代方案。

#include <queue>
#include <vector>
#include <iostream>
#include <iterator>
#include <algorithm>

std::vector<int> largestIndices(const std::vector<double>& values, int k) {
    std::vector<int> ret;

    std::vector<std::pair<double, int>> q;
    int index = -1;
    std::transform(values.begin(), values.end(), std::back_inserter(q), [&](double val) {return std::make_pair(val, ++index); });
    auto functor = [](const std::pair<double, int>& a, const std::pair<double, int>& b) { return b.first > a.first; };
    std::make_heap(q.begin(), q.end(), functor);
    for (auto i = 0; i < k && i<values.size(); i++) {
        std::pop_heap(q.begin(), q.end(), functor);
        ret.push_back(q.back().second);
        q.pop_back();
    }

    return ret;
}

int main()
{
    std::vector<double> values = { 7,6,3,4,5,2,1,0 };
    auto ret=largestIndices(values, 4);
    std::copy(ret.begin(), ret.end(), std::ostream_iterator<int>(std::cout, "\n"));
}
于 2016-08-26T10:55:00.023 回答
0

您可以O(n)通过单次订单统计计算及时完成此操作:

  • rk-th 阶统计量
  • 初始化两个空列表biggerequal
  • 对于每个索引i
    • 如果array[i] > r,添加ibigger
    • 如果array[i] = r,添加iequal
  • 丢弃元素,equal直到两个列表的长度之和为k
  • 返回两个列表的连接。

当然,如果所有项目都不同,您只需要一个列表。如果需要,您可以采取一些技巧将两个列表合并为一个,尽管这会使代码更加复杂。

于 2016-07-15T08:49:06.747 回答