Индексы k наибольших элементов в массиве unsorted length n

Мне нужно найти индексы k наибольших элементов несортированного длины n, array/vector в С++, с k < п. Я видел, как использовать nth_element() для поиска k-й статистики, но я не уверен, что использование этого является правильным выбором для моей проблемы, поскольку мне кажется, что мне нужно будет сделать k вызовов nth_statistic, что, я думаю у него будет сложность O (kn), которая может быть такой же хорошей, как она может быть получена? Или есть способ сделать это только в O (n)?

Реализация без nth_element() кажется, что мне придется перебирать весь массив один раз, заполняя список индексов самых больших элементов на каждом шаге.

Есть ли что-нибудь в стандартной библиотеке С++, которая делает это одним лайнером или каким-либо умным способом реализовать это самостоятельно всего за пару строк? В моем конкретном случае k = 3 и n = 6, поэтому эффективность не вызывает большого беспокойства, но было бы неплохо найти чистый и эффективный способ сделать это для произвольных k и n.

Похоже, Отметить верхние N элементов несортированного массива - это, вероятно, самая близкая публикация, которую я могу найти на SO, сообщения, которые есть в Python и PHP.

Ответ 1

Вот моя реализация, которая делает то, что я хочу, и я думаю, что это разумно эффективно:

#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

Вопрос имеет частичный ответ; то есть std::nth_element возвращает "n-ю статистику" с тем свойством, что ни один из элементов, предшествующих n-му, больше, чем один, и ни один из следующих за ним элементов меньше.

Поэтому для вызова k наибольших элементов достаточно всего одного вызова std::nth_element. Сложность времени будет O (n), которая теоретически является самой маленькой, так как вы должны посетить каждый элемент хотя бы один раз, чтобы найти наименьший (или в этом случае k-самый маленький) элемент (ы). Если вам нужно, чтобы эти k элементов были заказаны, вам необходимо заказать их, которые будут O (k log (k)). Итак, в общем случае O (n + k log (k)).

Ответ 3

Это должна быть улучшенная версия @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

Ответ 4

Вы можете использовать основу алгоритма quicksort, чтобы делать то, что вам нужно, за исключением того, что вместо переупорядочения разделов вы можете избавиться от записей, выпавших из вашего желаемого диапазона.

Он упоминается как "быстрый выбор" и вот реализация С++:

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).

Ответ 5

Стандартная библиотека не даст вам список индексов (он был разработан, чтобы избежать передачи избыточных данных). Однако, если вас интересуют n самых больших элементов, используйте какое-то разделение (оба std::partition и std::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;
}

Ответ 6

Вы можете сделать это в O(n) времени с помощью расчета статистики по одному заказу:

  • Пусть r - статистика k -го порядка
  • Инициализируйте два пустых списка bigger и equal.
  • Для каждого индекса i:
    • Если array[i] > r, добавьте i в bigger
    • Если array[i] = r, добавьте i в equal
  • Отменить элементы из equal, пока сумма длин двух списков не будет k
  • Верните конкатенацию двух списков.

Естественно, вам нужен только один список, если все элементы различны. И если нужно, вы могли бы сделать трюки, чтобы объединить два списка в один, хотя это сделало бы код более сложным.

Ответ 7

Несмотря на то, что следующий код может не соответствовать требуемым ограничениям сложности, он может быть интересной альтернативой для вышеупомянутой очереди приоритетов.

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