Какая практическая разница между std:: nth_element и std:: sort?
Я смотрел на алгоритм std:: nth_element, который, по-видимому:
Переупорядочивает элементы в диапазоне [первый, последний], таким образом, чтобы элемент на полученной n-й позиции является элементом, который будет в этом положении в отсортированной последовательности, причем ни один из элементов предшествующий ему, больше, и ни один из элементов, следующих за ним меньше. Ни предшествующие ему элементы, ни элементы после этого гарантируется, что они будут заказаны.
Однако с моим компилятором выполняется следующее:
vector<int> myvector;
srand(GetTickCount());
// set some values:
for ( int i = 0; i < 10; i++ )
myvector.push_back(rand());
// nth_element around the 4th element
nth_element (myvector.begin(), myvector.begin()+4, myvector.end());
// print results
for (auto it=myvector.begin(); it!=myvector.end(); ++it)
cout << " " << *it;
cout << endl;
Всегда возвращает полностью отсортированный список целых чисел точно так же, как и std:: sort. Я что-то упускаю? Для чего этот алгоритм полезен?
РЕДАКТИРОВАТЬ: Ok, следующий пример с использованием гораздо большего набора показывает, что существует большая разница:
vector<int> myvector;
srand(GetTickCount());
// set some values:
for ( int i = 0; i < RAND_MAX; i++ )
myvector.push_back(rand());
// nth_element around the 4th element
nth_element (myvector.begin(), myvector.begin()+rand(), myvector.end());
vector<int> copy = myvector;
std::sort(myvector.begin(), myvector.end());
cout << (myvector == copy ? "true" : "false") << endl;
Ответы
Ответ 1
Это действительно справедливо для std::nth_element
, чтобы отсортировать весь диапазон для выполнения документированной семантики - однако это приведет к сбою при выполнении требуемого сложность (линейная). Главное, что он может это сделать, но этого не нужно.
Это означает, что std::nth_element
может выйти из строя раньше - как только он сможет определить, что будет n'th
элементом вашего диапазона, он может остановиться. Например, для диапазона
[9,3,6,2,1,7,8,5,4,0]
попросив дать вам четвертый элемент, может дать что-то вроде
[2,0,1,3,8,5,6,9,7,4]
Список был частично отсортирован, достаточно хорош, чтобы можно было сказать, что четвертый элемент в порядке будет 3
.
Следовательно, если вы хотите ответить, "какой номер является четвертым наименьшим" или "который является четырьмя наименьшими", тогда std::nth_element
является вашим другом.
Если вы хотите получить четыре наименьших числа, чтобы вы могли использовать std::partial_sort
.
Ответ 2
Реализация std:: nth_element выглядит следующим образом:
void _Nth_element(_RanIt _First, _RanIt _Nth, _RanIt _Last, _Pr _Pred)
{
for (; _ISORT_MAX < _Last - _First; )
{ // divide and conquer, ordering partition containing Nth
pair<_RanIt, _RanIt> _Mid =
_Unguarded_partition(_First, _Last, _Pred);
if (_Mid.second <= _Nth)
_First = _Mid.second;
else if (_Mid.first <= _Nth)
return; // Nth inside fat pivot, done
else
_Last = _Mid.first;
}
_Insertion_sort(_First, _Last, _Pred); // sort any remainder
}
где ISORT_MAX определяется как 32.
Таким образом, если ваша последовательность является дробителем, чем 32 элемента, она просто выполняет InsertionSort.
Поэтому ваша короткая последовательность полностью отсортирована.
Ответ 3
std::sort
сортирует все элементы. std::nth_elenemt
нет. Он просто помещает n-й элемент в n-ое положение, с меньшими или равными элементами на одной стороне и большими или равными элементами - с другой. Он используется, если вы хотите найти n-й элемент (очевидно) или хотите, чтобы n наименьших или самых больших элементов. Полная сортировка удовлетворяет этим требованиям.
Итак, почему бы просто не выполнить полный сортировку и получить n-й элемент? Поскольку std::nth_element
имеет требование иметь сложность O (N), тогда как std::sort
- O (Nlog (N)). std::sort
не может удовлетворить требованиям сложности std::nth_element
.
Если вам не нужна полная сортировка диапазона, целесообразно использовать его.
Что касается вашего примера, когда я запускаю аналогичный код в GCC 4.7, я получаю ожидаемые результаты:
for ( int i = 0; i < 10; i++ )
myvector.push_back(rand()%32); // make the numbers small
cout << myvector << "\n";
// nth_element around the 4th element
nth_element (myvector.begin(), myvector.begin()+4, myvector.end());
cout << myvector << "\n";
std::sort(myvector.begin(), myvector.end());
cout << myvector << "\n";
производит
{ 7, 6, 9, 19, 17, 31, 10, 12, 9, 13 }
{ 9, 6, 9, 7, 10, 12, 13, 31, 17, 19 }
{ 6, 7, 9, 9, 10, 12, 13, 17, 19, 31 }
^
где я использовал созданный на заказ ostream operator<<
, чтобы распечатать результаты.