std :: nth_element和std :: sort之间的实际区别是什么?

时间:2012-04-27 14:23:37

标签: c++

我一直在看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;

4 个答案:

答案 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;
}