未排序长度n数组中k个最大元素的索引

时间:2013-02-15 20:32:25

标签: c++ arrays max indices

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

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

标准C ++库中是否有任何内容可以让它成为一个单行或任何聪明的方法来自己实现这几行?在我的特定情况下,k = 3,n = 6,因此效率不是一个大问题,但找到一个干净有效的方法来为任意k和n做这个很好。

看起来Mark the top N elements of an unsorted array可能是我在SO上发现的最接近的帖子,其中的帖子有Python和PHP。

7 个答案:

答案 0 :(得分:8)

这是我的实现,它做了我想要的,我认为是合理有效的:

#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

答案 1 :(得分:6)

这个问题有部分答案;即std::nth_element返回&#34;第n个统计数据&#34;具有第n个之前的元素都不大于的属性,并且后面的元素都不是

因此,只需一次调用std::nth_element即可获得k个最大的元素。时间复杂度将是O(n),理论上它是最小的,因为你必须至少一次访问每个元素以找到最小(或在这种情况下是k-最小)元素。如果您需要订购这些k元素,那么您需要订购它们为O(k log(k))。所以,总共O(n + k log(k))。

答案 2 :(得分:6)

这应该是@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

答案 3 :(得分:3)

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

它被称为“快速选择”和here is a C++ implementation

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)平均运行时间;由于选择枢轴的方法,它共享quicksort的最坏情况运行时间。到optimizing the pivot choice,你最坏的情况也会变成O(n)。

答案 4 :(得分:2)

标准库不会为您提供索引列表(它旨在避免传递冗余数据)。但是,如果您对n个最大元素感兴趣,请使用某种分区(std::partitionstd::nth_element都是O(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;
}

答案 5 :(得分:0)

您可以使用单一订单统计计算在O(n)时间内执行此操作:

  • r成为k - 阶统计
  • 初始化两个空列表biggerequal
  • 对于每个索引i
    • 如果array[i] > r,请将i添加到bigger
    • 如果array[i] = r,请将i添加到equal
  • 丢弃equal中的元素,直到两个列表的长度总和为k
  • 返回两个列表的串联。

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

答案 6 :(得分: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"));
}