Мне нужно найти индексы k самых больших элементов несортированного, длины n, массива / вектора в C ++, с k < п. Я видел, как использовать nth_element (), чтобы найти k-ую статистику, но я не уверен, что использование этого является правильным выбором для моей проблемы, так как кажется, что мне нужно будет сделать k вызовов nth_statistic, что, я думаю, это будет иметь сложность O (kn), которая может быть настолько хорошей, насколько это возможно? Или есть способ сделать это только в O (n)?
Реализация его без nth_element () выглядит так, как будто мне придется итерировать весь массив один раз, заполняя список индексов самых больших элементов на каждом шаге.
Есть ли что-нибудь в стандартной библиотеке C ++, которая делает это однострочным или каким-либо умным способом реализовать это самостоятельно всего за пару строк? В моем конкретном случае k = 3 и n = 6, поэтому эффективность не представляет большой проблемы, но было бы неплохо найти чистый и эффективный способ сделать это для произвольных k и n.
Это выглядит как Пометить верхние N элементов несортированного массива Вероятно, это самая близкая публикация, которую я могу найти на SO, она есть на Python и PHP.
Вот моя реализация, которая делает то, что я хочу, и я думаю, что это достаточно эффективно:
#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
Вопрос имеет частичный ответ; то есть std::nth_element
возвращает «n-ую статистику» со свойством, которое ни один из элементов, предшествующих n-му, не превосходит его, а также ни один из следующих за ним элементов не меньше.
Следовательно, всего один звонок в std::nth_element
достаточно, чтобы получить k самых больших элементов. Временная сложность будет O (n), что теоретически является наименьшим, поскольку вам нужно посетить каждый элемент хотя бы один раз, чтобы найти наименьший (или в данном случае k-наименьший) элемент (ы). Если вам нужно, чтобы эти k элементов были упорядочены, то вам нужно упорядочить их, что будет O (k log (k)). Итак, всего O (n + k log (k)).
Это должна быть улучшенная версия @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
Вы можете использовать базовый алгоритм быстрой сортировки, чтобы делать то, что вам нужно, за исключением того, что вместо переупорядочения разделов вы можете избавиться от записей, выпадающих из желаемого диапазона.
Это упоминается как «быстрый выбор» и здесь реализация 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); благодаря методу выбора pivot, он разделяет время выполнения быстрой сортировки в худшем случае. От оптимизации выбора поворота, ваш худший случай также становится O (n).
Стандартная библиотека не даст вам список индексов (она была разработана, чтобы избежать передачи избыточных данных). Однако, если вы заинтересованы в n крупнейших элементах, используйте какое-то разбиение (оба std::partition
а также std::nth_element
находятся на)):
#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;
}
Вы можете сделать это в O(n)
время с вычислением статистики одного заказа:
r
быть k
-статистическая статистикаbigger
а также equal
,i
:
array[i] > r
, добавлять i
в bigger
array[i] = r
, добавлять i
в equal
equal
пока сумма длин двух списков не станет k
Естественно, вам нужен только один список, если все элементы различны. И если необходимо, вы можете сделать трюки, чтобы объединить два списка в один, хотя это усложнит код.
Даже если следующий код может не удовлетворять желаемым ограничениям сложности, он может быть интересной альтернативой для вышеупомянутой очереди приоритетов.
#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"));
}