C++ 具有固定整数数的排序向量

C++ 具有固定整数数的排序向量,c++,algorithm,sorting,integer,C++,Algorithm,Sorting,Integer,Vectorlist vl的长度为100000000,最多有101个不同的整数值。最好最快的排序算法是什么 我尝试了计数排序(桶排序),…,但速度不够快。每个整数(+-)都是有效的。100000000、101个不同的整数是随机生成的。谢谢你的回复!我最好的算法是0.620s左右。使用无序集查找唯一值,然后将这些唯一值放入向量中并对其排序;然后将原稿放入一个无序的\u multiset来计算值,类似于: vector<int> v; // fill v with values unor

Vectorlist vl的长度为100000000,最多有101个不同的整数值。最好最快的排序算法是什么


我尝试了计数排序(桶排序),…,但速度不够快。每个整数(+-)都是有效的。100000000、101个不同的整数是随机生成的。谢谢你的回复!我最好的算法是0.620s左右。

使用
无序集
查找唯一值,然后将这些唯一值放入
向量
中并对其排序;然后将原稿放入一个
无序的\u multiset
来计算值,类似于:

vector<int> v;
// fill v with values
unordered_set<int> s(begin(v), end(v));
vector<int> sorted_v(begin(s), end(s));
sort(begin(sorted_v), end(sorted_v));
unordered_multiset<int> v_count(begin(v), end(v));
for (size_t i = 0; i < sorted_v.size(); ++i)
    cout << "For the " << i << "th value == " << sorted_v[i] << " there are " << v_count.count(v[i]) << " of them." << endl;
向量v; //用值填充v 无序集合s(开始(v),结束(v)); 向量排序_v(开始、结束); 排序(开始(排序的)、结束(排序的); 无序多集计数(开始(v),结束(v)); 对于(大小i=0;i我相信对于这个任务来说,计数排序是最合适的。 但是,如果你只需要打印尽可能快的整数排序列表,考虑不要保存整数,而只是在map中的计数器。

根据(参见算法比较表),我们应该使用计数排序,因为我们没有这么多不同的值。 首先,我假设我们的值是ints 0-100,并使用以下代码:

void sort(std::vector<int>& v)
{
    double start = std::clock();
    int* table = new int[MAX];
    for (int i = 0; i < MAX; ++i)
    {
        table[i] = 0;
    }
    for (int i = 0; i < size; ++i)
    {
        ++table[v[i]];
    }
    int cur = 0;
    for (int i = 0; i < MAX; ++i)
    {
        for (int j = 0; j < table[i]; ++j)
        {
            v[cur++] = i;
        }
    }
    delete[] table;
    std::cout << "count sort over char array took " << (std::clock() - start) / CLOCKS_PER_SEC << " s" << std::endl;
}
这将产生大约
0.076s

其次,假设我们的值不是整数0-100,我使用以下算法:

  • 找到所有101个不同的数字(考虑均匀分布)
  • 对这些数字进行排序
  • 执行计数排序时,在此数组中查找100000000个数字

不幸的是,目前我没有时间实现和检查,但我确信答案就在那里。

这是上面其他一些用户描述的算法的完整实现。 算法的总复杂度为O(n)

有许多魔术和半魔术优化可获得此结果:

  • 使用自己的普通散列函数:-50%
  • 使用100作为哈希表存储桶计数的乘数:-50%
  • 编译为x64而不是32位代码(x86):-25%
  • 使用C++11 foreach代替迭代器的等效值:-33%
除了Sergey的之外,您还可以使用多个线程并行运行计数,这将使进程至少加快2倍

因此,不是:

std::unordered_map<int, size_t> counts;
counts.reserve(nExpectedMaxDifferentValues * 100);
for (const auto x : v)
    ++counts[x];

在我的设置中,上面的代码导致执行时间为390ms,而不是860ms,并且还可以通过并行聚合部分计数来提高执行时间。

我认为插入到树映射中的速度最快,其中每个值都是给定键的插入次数。本质上,我建议对可压缩数据进行插入排序。在C++中,可以使用<代码> unordeDeMAP < /COD>来对值进行哈希和保持每个不同值的计数。@ Bathsheba同意 map < /COD>将得到自由排序,但是如果不同值的数目较大(如M),则我们必须每次都要记录日志(M)。所以时间是nlog(m)。但是在散列的情况下,它将是n+mlog(m),我认为如果我们不帮助这个吸血鬼会更好。计数显然是指定条件下最快的排序方法。如果你正确地尝试了,但速度“不够快”,那么什么也做不到。但你到底做了什么,你称之为“计数排序”?也许你做错了。为什么你在
counts.reserve(nExpectedMaxDifferentValues*100)中预订的数量比实际需要的多100倍?只需保留
nexpectedmaxDifferentitValue
会降低系数为2的代码速度,但我想知道为什么…我不确定100%。我有100次乘法,因为它使整个算法快了两倍。我以前有因子2,但算法慢了2倍。我确信因子2应该足以停止考虑散列冲突,但在本例中并非如此。我发现了额外的优化,因此并行代码平均工作时间为160毫秒。我还将更新您的帖子,使其具有跨平台代码。
#include <vector>
#include <unordered_map>
#include <algorithm>
#include <cstdint>

void special_sort(std::vector<int>& v, const size_t nExpectedMaxDifferentValues)
{
    typedef int_fast32_t Value;
    typedef size_t Count;
    static_assert(sizeof(Value) >= sizeof(int), "please define Value to int on this platform");

    struct ValHash
    {
        inline std::size_t operator()(const Value k) const
        {
            return k;
        }
    };

    std::unordered_map<Value, Count, ValHash> counts;

    counts.reserve(nExpectedMaxDifferentValues * 100);
    for (const auto x : v)
        ++counts[x];

    std::vector<Value> sorted_numbers;
    sorted_numbers.reserve(counts.size());
    for (const auto& p : counts)
        sorted_numbers.push_back(p.first);

    std::sort(std::begin(sorted_numbers), std::end(sorted_numbers));

    // fill vector with sorted data:
    int* p = v.data();
    for (const auto x : sorted_numbers)
    {
        for (Count i = counts[x]; i > 0; --i)
        {
            *p++ = x;
        }
    }
}
#include <random>
#include <limits>
#include <time.h>
#include <iostream>

int main()
{
    std::cout << "Initialize..." << std::endl;
    const size_t N = 100000000;
    const size_t M = 101;

    std::mt19937 gen(5); // use constant to easily reproduce the test
    std::uniform_int_distribution<int> disInt(std::numeric_limits<int>::min(), std::numeric_limits<int>::max());
    std::vector<int> v1;
    v1.reserve(M);

    for (size_t i = 0; i < M; ++i)
        v1.push_back(disInt(gen));

    std::uniform_int_distribution<size_t> disIndex(0, M-1);
    std::vector<int> v2;
    v2.reserve(N);

    for (size_t i = 0; i < N; ++i)
        v2.push_back(v1[disIndex(gen)]);

    std::cout << "Sort..." << std::endl;
    const clock_t begin_time = clock();

    special_sort(v2, M);

    const double seconds = double(clock() - begin_time) / CLOCKS_PER_SEC;
    std::cout << "Sorting took " << int(seconds * 1000) << " ms" << std::endl;
    return 0;
}
Initialize...
Sort...
Sorting took 374 ms
std::unordered_map<int, size_t> counts;
counts.reserve(nExpectedMaxDifferentValues * 100);
for (const auto x : v)
    ++counts[x];
// Spawn 8 threads and spread the work
const int numberOfThreads = 8;
PartialResult partialResults[numberOfThreads];
HANDLE threadHandles[numberOfThreads];
const size_t partialSize = v.size() / numberOfThreads;
std::vector<int>::iterator it = v.begin();
for (auto i = 0; i < numberOfThreads; i++)
{
    partialResults[i].reserve = nExpectedMaxDifferentValues * 100;
    partialResults[i].begin = it;
    it += partialSize;
    partialResults[i].end = (i == numberOfThreads - 1) ? v.end() : it;
    threadHandles[i] = ::CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)partial_count, (LPVOID)&partialResults[i], 0, NULL);
}

// Wait for all threads to finish
::WaitForMultipleObjects(numberOfThreads, threadHandles, TRUE, INFINITE);
for (auto i = 0; i < numberOfThreads; i++)
    ::CloseHandle(threadHandles[i]);

// Aggregate counts (this could also be done in parallel)
std::unordered_map<int, size_t> counts;
counts.reserve(nExpectedMaxDifferentValues * 100);
for (auto i = 0; i < numberOfThreads; i++)
    for (const auto x : partialResults[i].counts)
        counts[x.first] += x.second;
struct PartialResult {
    std::unordered_map<int, size_t> counts;
    std::vector<int>::iterator begin;
    std::vector<int>::iterator end;
    size_t reserve;
};

DWORD WINAPI partial_count(_In_ LPVOID lpParameter)
{
    auto partialResult = (PartialResult*)lpParameter;
    partialResult->counts.reserve(partialResult->reserve);
    for (auto it = partialResult->begin; it < partialResult->end; it++)
        ++partialResult->counts[*it];
    return 0;
}