我一直在看std :: nth_element算法,显然是:
以这样的方式重新排列[first,last]范围内的元素 得到的第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相同的方式返回完全排序的整数列表。我错过了什么吗?这个算法对什么有用?
编辑:好的以下示例使用更大的集合表明存在很大差异:
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;
答案 0 :(得分:38)
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
。
答案 1 :(得分:7)
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。 因此,您的短序列已完全排序。
答案 2 :(得分:6)
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<<
来打印结果。
答案 3 :(得分:0)
我比较了在大型矢量(512MB)上随机无符号long long并取其中间元素时,std :: sort与std :: nth_element的执行时间。是的,我知道它是O(N log(N))vs O(N),无论如何,我希望std :: nth_element(mid)的速度大约是std :: sort的两倍,因为它应该对“排序”中大约一半的元素。结果让我有些惊讶,这就是为什么我分享它们:
timeSort = 217407 (msec)
timeNthElement = 18218 (msec)
std :: sort慢12倍
这是我使用的一段代码(它使用的是windows.h):
#include <windows.h>
#include <string>
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
#include <random>
int main()
{
static const size_t NUMELEM = 512 * 1024 * 1024;
static const size_t NUMITER = 3;
std::vector<unsigned long long> vec1(NUMELEM);
std::vector<unsigned long long> vec2(NUMELEM);
std::random_device rd;
std::mt19937 rand(rd());
std::uniform_int_distribution<unsigned long long> dist(0, NUMELEM * 2);
unsigned long long timeNthElement = 0;
unsigned long long timeSort = 0;
for (size_t j = 0; j < NUMITER; ++j)
{
for (size_t i = 0; i < NUMELEM; ++i)
{
unsigned long long val = dist(rand);
vec1[i] = val;
vec2[i] = val;
}
ULONGLONG t1 = GetTickCount64();
std::sort(begin(vec1), end(vec1));
ULONGLONG t2 = GetTickCount64();
std::nth_element(begin(vec2), begin(vec2)+NUMELEM/2, end(vec2));
ULONGLONG t3 = GetTickCount64();
if (vec1[NUMELEM / 2] != vec2[NUMELEM / 2])
{
Sleep(0); // I put a breakpoint here but of course never caught it...
}
timeSort += t2 - t1;
timeNthElement += t3 - t2;
}
std::cout << "timeSort = " << timeSort << std::endl;
std::cout << "timeNthElement = " << timeNthElement << std::endl;
return 0;
}