2013-02-15 100 views
12

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

在没有使用nth_element()的情况下实现它看起来好像我必须遍历整个数组一次,填充每个步骤中最大元素的索引列表。

标准C++库中是否有任何东西使得这是一种单线或任何聪明的方式来实现这一点,我自己在几行?在我的具体情况中,k = 3,n = 6,所以效率不是一个大问题,但找到一个干净而有效的方法来为任意k和n做这件事是很好的。

它看起来像Mark the top N elements of an unsorted array可能是我可以找到的最接近的帖子,所发布的内容有Python和PHP。

+0

你可以修改矢量吗? nth_element将进行部分排序,因此它会修改向量。 – amdn 2013-02-15 23:28:38

+0

可以修改向量,但最终结果需要是k个最大元素的索引(原始向量的索引)。 – hazelnusse 2013-02-16 02:19:37

+0

这只是一个选择算法。通常你会使用堆选择或快速选择。有关类似问题,请参阅http://stackoverflow.com/q/7746648/56778。有一个好的C++解决方案的答案。 (使用priority_queue) – 2013-02-20 20:47:15

回答

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

OUTPUT:

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

编辑

这个特定的实现有一个O(n)的平均运行时间;由于选择主键的方法,它共享快速排序的最坏情况运行时间。通过optimizing the pivot choice,你最坏的情况也会变成O(n)。

1

标准库不会为您提供索引列表(它旨在避免传递冗余数据)。但是,如果你有兴趣在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; 
} 
+0

我特别需要这些指标。 – hazelnusse 2013-02-15 23:03:39

+0

@hazelnusse你可以为你的元素定义一个结构类型,存储值和原始索引,同时为它定义比较器。 – ziyuang 2013-03-08 01:27:26

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 
+2

我使用nth_element和partial_sort来定义一个实现,并使用自定义比较器...您的实现更快。 – amdn 2013-02-18 03:31:46

+6

无需将所有项目添加到优先级队列。这使得算法O(n log n)。如果您不添加比已经在队列中的最小项目更小的东西,则可以在O(n log k)中完成。请参阅http://stackoverflow.com/q/7746648/56778进行讨论。 – 2013-02-20 20:52:06

+0

@JimMischel也许我错过了一些东西,但据我所知,如果我只添加比队列中最小元素更大的元素,我最终可能会丢失一些k-top元素。 E.g如果添加到优先级队列中的第一个元素是最大元素,那么它同时是队列中最小的元素,并且会导致算法不添加任何其他元素。 – spurra 2015-01-21 16:59:31

6

问题具有部分答案;即std::nth_element返回“第n个统计量”,其性质为前面第n个元素中的任何一个都大于它后面的元素都不会少于

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

+3

这找到了k个最大的元素,而OP的要求是找到k个最大的索引。 – 2015-12-01 10:57:28

+3

那么,你是对的,(再次看问题)我不知道我为什么首先给出了这个答案,为什么人们对它进行了投票。但最有可能的是,他们误解了和我一样的问题,显然,这个答案以某种方式帮助了他们,所以我会保持这样。 – 2015-12-01 14:27:04

4

这应该是其在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; 
    } 
} 
0
执行代替@hazelnusse的改进版本

可以在这样做O(n)单次订单统计计算时间:

  • rk个顺序统计
  • 初始化两个空列表biggerequal
  • 对于每个索引i
    • 如果array[i] > r,添加ibigger
    • 如果array[i] = r,添加从equaliequal
  • 丢弃元素,直到这两个列表的长度的总和是k
  • 返回两个列表的连接。

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

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")); 
}