Что такое nth_element и что именно он делает? и как это реализовать

#c #algorithm #nth-element

Вопрос:

Я почти понял многие алгоритмы STL, пока не добрался до алгоритма std::nth_element . Я зациклился на этом; я не знаю, как это работает, и это действительно работает точно.

Для образования и понимания, может ли кто-нибудь объяснить мне, как std::nth_element работает алгоритм?

 std::vector<int> v{ 9, 3, 6, 2, 1, 7, 8, 5, 4, 0 };
std::nth_element(v.begin(), v.begin()   2, v.end());

for (auto i : v)
    std::cout << i << " ";
std::cout << 'n';
 

Вывод:

 1 0 2 3 6 7 8 5 4 9 
 
  • Так где же nth здесь элемент?
  • Как и что делает алгоритм?
  • Выполняет ли он какую-то частичную сортировку?

Вот некоторые объяснения из cppreference.com:

nth_element является алгоритмом частичной сортировки, который переставляет элементы в [первый, последний) таким образом, что:

  • Элемент, на который указывает nth, изменяется на любой элемент, который находился бы в этом положении, если бы [первый, последний) был отсортирован.
  • Все элементы до этого нового n-го элемента меньше или равны элементам после нового n-го элемента. Более формально, nth_element частично сортирует диапазон [первый, последний) в порядке возрастания, чтобы условие !(*j < *i) (для первой версии или comp(*j, *i) == false для второй версии) выполнялось для любого i в диапазоне [первый, n-й) и для любого j в диапазоне [n-й, последний). Элемент, помещенный в n-ю позицию, является именно тем элементом, который находился бы в этой позиции, если бы диапазон был полностью отсортирован.

n-й может быть конечным итератором, в этом случае функция не действует.

  • Я все еще в замешательстве по этому поводу. Что такое n-й элемент и как реализовать подобный возможный алгоритм?. Ради образования я имитировал многие алгоритмы STL. Большое вам спасибо!

Комментарии:

1. So where is nth element here? Что вы подразумеваете под «где»? How and what the algorithm does? Именно то, что указано в документации, которую вы процитировали. Does it do some sort of partial sorting? Хорошо… nth_element is a partial sorting algorithm

2. Если у вас возникли проблемы с пониманием документации, пожалуйста, сообщите нам, какие строки/утверждения вы не понимаете.

3. Возможно, более поучительным вопросом для ответа было бы «при каких обстоятельствах кто-то счел бы призвание nth_element() полезным»? (предположительно, функция была написана не только для увеличения STL; должно быть, существовала какая-то общая проблема, которую кто-то хотел решить, что побудило их написать ее и включить в STL)

4. @ItachiUchiwa: n-й элемент относится к позиции, а не к значению. v.begin() 2 является третьим элементом (индекс 2 , основанный на 0). Если бы весь массив был отсортирован, 2 он появился бы в этой позиции, и nth_element это произошло бы. Положение всех остальных элементов полуслучайно, за исключением гарантии того, что все элементы, меньшие, чем тот, который заканчивается в индексе 2 , предшествуют ему, а все те, которые больше, чем он, следуют за ним. Рекомендуемый алгоритм-интроселект.

5. @JeremyFriesner: Я полагаю, что у него есть варианты использования, похожие на этот partial_sort . Вам нужно разделить самые большие и самые маленькие элементы на некотором пороге ранжирования, но вам не нужно сортировать элементы, чтобы получить полезные результаты. Например, чтобы получить среднее значение средних 90% набора данных, вам нужно отделить 5% с обеих сторон как выбросы, но средние 90% не нужно сортировать. Вы можете использовать nth_element один раз, чтобы отделить нижние 5%, затем еще раз (на 95% справа от оси), чтобы отделить верхние 5%. Затем линейный проход для вычисления среднего значения. Три O(n) шага, никакого O(n log n) рода вообще.

Ответ №1:

Так где же здесь n-й элемент?

n-й элемент-это индекс 2 at 2 , потому что это то, что вы просили, когда проходили begin() 2 .

Элемент, на который указывает nth, изменяется на любой элемент, который находился бы в этом положении, если бы [первый, последний) был отсортирован.

Это означает, что если бы вектор был отсортирован, порядок элементов был бы

 0 1 2 3 4 5 6 7 8 9 
    ^--- begin()   2
 

Вы попросили иметь 3-й по величине элемент в индексе 2 (3-я позиция), и это то, что делает алгоритм.

Кроме того, он помещает все элементы меньшего размера спереди и все элементы большего размера сзади:

!(*j < *i) (для первой версии или comp(*j, *i) == false для второй версии) выполняется для любого i в диапазоне [первый, n-й) и для любого j в диапазоне [n-й, последний).

Давайте использовать индексы, а не итераторы, тогда для любого i < 2 и для любого j > 2 это так v[i] < v[j] . Другими словами, 1 и 0 то и другое меньше, чем любой элемент в 2 3 6 7 8 5 4 9 .

Комментарии:

1. Но значение at v.begin() 2 6 не 2 таково ? 2 находится в моем выводе не в исходной последовательности.

2. @Итачиучива «Элемент, на который указывает n-й, изменяется на любой элемент, который находился бы в этой позиции, если бы [первый, последний) был отсортирован». 2-это значение, которое будет отображаться в индексе 2 после сортировки вектора

3. @Итачи Учива см. правку

4. И последнее: могу ли я реализовать аналогичный алгоритм в образовательных целях?

5. @Итачиучива, ты можешь? Я не могу ответить на этот вопрос. На странице cppreference есть ссылки на реализации: libstdc и libc

Ответ №2:

Я сначала объясню свой код, прежде чем приступать к вашей проблеме

например, у меня есть такой код

 int m_array_biasa[8] {3,2,10,45,33,56,23,47};
 

и я обычно использую его, как

 std::nth_element(m_array_biasa, m_array_biasa   4, m_array_biasa   8);
 

итак, n-й элемент здесь равен 4[33], правило std::nth_element заключается в том, что число слева от n-го должно быть меньше или равно, а число справа должно быть больше n-го

и не забывайте, что данные должны быть отсортированы от маленьких до больших (по умолчанию)

итак, данные, которые изначально были

3,2,10,45,33,56,23,47

изменено на

2 3 10 23 33 45 47 56

мое n-е число равно 4[33], поэтому применяются вышеуказанные правила (без учета результата сортировки).

и в результате получается

3 2 10 23 33 56 45 47

обратите внимание выше, позиция 33 не изменилась, но иногда это немного сбивает с толку, например, мы меняем 33 на 1, затем результат

2 1 3 10 23 45 47 56

что здесь произошло, почему число 1 переместилось (заменено на 23), почему оно не осталось таким, как предыдущее число 33, я уже говорил , что сначала мы должны отсортировать данные (см. Сортировку выше), получается, что индекс nth[4] равен 23, затем число 1 заменяется на число 23, почему его следует заменить?, см. Правило nth_element

теперь перейдем к вашему вопросу.

 std::vector<int> v{ 9, 3, 6, 2, 1, 7, 8, 5, 4, 0 };
std::nth_element(v.begin(), v.begin()   2, v.end());
 

v. begin() содержит 9, v. begin() 2 содержит 6, помните, что nth_element сначала отсортирует его

0 1 2 3 4 5 6 7 8 9

и ваш результат таков

1 0 2 3 6 7 8 5 4 9

n-е[2] выше (в соответствии с вашим v. begin() 2)p равно 2, поэтому 2 здесь похоже на ссылку для других данных, данные до 2 должны быть меньше, чем это, и после этого должно быть больше, чем это