【问题标题】:Remove smallest non-unique value from vector从向量中删除最小的非唯一值
【发布时间】:2015-06-17 16:19:16
【问题描述】:

我有一个未排序的双精度向量(实际上是在这种情况下使用的具有双精度成员的对象)。我需要从这个向量中删除最小的非唯一值。但是,不能保证存在非唯一值。允许对范围进行排序。

与往常一样,我从寻找 std::algorithm 开始并找到了 std::unique。在我的第一个想法中,我会将它与 std::sort 结合使用,将所有非唯一值移动到向量的末尾,然后在非唯一值上使用 min_element 。但是,std::unique 将使非唯一值最后处于未指定状态。事实上,我失去了所有非 POD 成员。

有没有人建议如何有效地做到这一点?高效地完成它很重要,因为代码被用于程序的瓶颈(这已经有点太慢了)。

【问题讨论】:

  • std::unique 仅适用于排序范围。
  • 我想不出任何“有效”的方法可以在未分类的范围内做到这一点,但我不是算法专家(这只是我完全鄙视编程工作面试的几个原因之一)。
  • 我知道,但这不是让非唯一值处于未指定状态的原因。此外,可以对向量进行排序,但在初始情况下并非如此。
  • @MichielUitHetBroek:确实,但这确实意味着您的测试毫无意义;)
  • @MichielUitHetBroek:好吧 :)

标签: c++ algorithm c++11 unique


【解决方案1】:

好吧,如果您可以对范围进行排序,那么这很容易。按升序对其进行排序,然后迭代直到遇到两个等效的相邻元素。完成。

类似这样的:

T findSmallestNonunique(std::vector<T> v)
{
   std::sort(std::begin(v), std::end(v));
   auto it = std::adjacent_find(std::begin(v), std::end(v));
   if (it == std::end(v))
      throw std::runtime_error("No such element found");
   return *it;
}

这里是a demonstration

#include <vector>
#include <algorithm>
#include <stdexcept>
#include <iostream>

template <typename Container>
typename Container::value_type findSmallestNonunique(Container c)
{
   std::sort(std::begin(c), std::end(c));
   auto it = std::adjacent_find(std::begin(c), std::end(c));

   if (it == std::end(c))
      throw std::runtime_error("No such element found");

   return *it;
}

int main(int argc, char** argv)
{
    std::vector<int> v;
    for (int i = 1; i < argc; i++)
        v.push_back(std::stoi(argv[i]));

    std::cout << findSmallestNonunique(v) << std::endl;
}

// g++ -std=c++14 -O2 -Wall -pedantic -pthread main.cpp \
// && ./a.out 1 2 2 3 4 5 5 6 7 \
// && ./a.out 5 2 8 3 9 3 0 1 4 \
// && ./a.out 5 8 9 2 0 1 3 4 7
// 
// 2
// 3
// terminate called after throwing an instance of 'std::runtime_error'
//   what():  No such element found

请注意,这里我不是在原地执行搜索,但我可以通过引用容器来完成。 (这取决于您是否被允许对原始输入进行排序。)

由于排序操作,这可能像 O(N×log(N)) 一样“糟糕”,但它简单且易于维护,并且不需要任何分配/副本(除了整个数据集的单个副本,如上所述,您可以轻松地完全避免)。如果您的输入很大,或者您希望在大多数情况下匹配失败,您可能想要使用其他东西。一如既往:个人资料

【讨论】:

  • @Ami:嗯,是因为排序?我承认,它可能和NlogN 一样糟糕。不过,我不需要散列,而且很简单。我认为这将取决于值类型是什么以及输入的时间长短,以及权衡的位置。一如既往地进行分析。
  • 我的解决方案中也添加了代码。看看这两种解决方案之间的临界点是什么确实会很有趣。我还想修改我上面有点强硬的声明——这不是“完全没有必要”,而是更适合较小的数组。
【解决方案2】:

您可以在(预期的)线性时间内完成此操作。

  • 使用unordered_map 对元素进行计数。这在值的数量上是(预期的)线性的。

  • 使用朴素循环在非唯一项中查找最小的项。

这是一个可能的实现:

#include <unordered_map>
#include <iostream>
#include <vector>

using namespace std;

int main()
{
    const vector<double> elems{1, 3.2, 3.2, 2};
    unordered_map<double, size_t> c;
    for(const double &d: elems)
        ++c[d];
    bool has = false;
    double min_;
    for(const auto &e: c)
        if(e.second > 1)
        {
            min_ = has? min(e.first, min_): e.first;
            has = true;
        }
    cout << boolalpha << has << " " << min_ << endl;
    return 0;
}

编辑 正如 Howard Hinnant 和 Lightness Races In Orbit 所指出的,这包含分配和散列。因此它将是线性的,但具有相对较大的因子。其他基于排序的解决方案可能更适合小尺寸。当/如果进行分析,使用好的分配器很重要,例如,Google's tcmalloc

【讨论】:

  • 这听起来真的慢。也许我误解了,但是您想将每个元素放在unordered_map 中吗?那是N个分配!这可能是 O(N),但常数非常大。也许一些代码会澄清?
  • 我也是这么想的。它可能具有更好的复杂性,但它确实做了很多……“事情”。也许是学术思考而不是实践思考的案例?不过,这又不是我真正的领域……
  • @HowardHinnant:确实 :-( [我认为它比“有趣”更“有趣”:P]
  • 可以在修改后的快速排序算法中使用该方法。拆分数组,直到到达长度为 10000 的部分(例如),然后使用具有开放寻址和固定大小的哈希映射来计算元素。这应该比排序稍微快一点,并且它使用的分配比原始方法少。
  • 我使用this code 进行测试。似乎对于 500 万个元素,使用哈希表要快 3 倍!
【解决方案3】:

首先,关于删除元素的任务,最难的是找到它,但实际上删除它很容易(与最后一个元素交换然后pop_back())。因此,我将只解决这一发现。另外,您提到对序列进行排序是可以接受的,但我认为不仅排序而且任何类型的重新排序都是可以接受的。

看一下快速排序算法。它选择一个随机元素,然后将序列划分为左右。如果您编写分区以便区分“less”和“not less”,您可以对序列进行部分排序并在运行中找到最小的重复项。

以下步骤应该可以完成这项工作:

  • 首先,选择一个随机枢轴并对序列进行分区。同时,您可以检测枢轴是否重复。请注意,如果您在此处找到重复项,您可以丢弃(!)任何更大的内容,因此您甚至不必为两个分区投资存储容量和带宽。
  • 然后,递归到较小元素的序列。
  • 如果较小的分区中有一组重复项,那么这些就是您的解决方案。
  • 如果第一个枢轴有重复,这就是您的解决方案。
  • 否则,递归到较大的元素以查找重复项。

相对于常规排序的优势在于,如果您在较小的数字中发现重复项,您不会对整个序列进行排序。但是,在一系列唯一数字上,您将对它们进行完全排序。与建议的使用 hashmap 计算元素相比,这确实具有更高的渐近复杂度。不过,它是否表现更好取决于您的实现和输入数据。

请注意,这要求可以对元素进行排序和比较。您提到您使用 double 值,当您在那里有 NaN 时,这些值是出了名的不好排序。我可以想象标准容器中的散列算法可以与 NaN 一起使用,因此使用散列映射进行计数还要多一点。

以下代码实现了上述算法。它使用一个递归函数来划分输入并查找重复项,从第二个函数调用,然后最终删除重复项:

#include <vector>
#include <algorithm>
#include <iostream>

template<typename iterator>
iterator partition_and_find_smallest_duplicate(iterator begin, iterator end)
{
    using std::swap;

    std::cout << "find_duplicate(";
    for (iterator it=begin; it!=end; ++it)
        std::cout << *it << ", ";
    std::cout << ")\n";
    if (begin == end)
        return end; // empty sequence

    // The range begin,end is split in four partitions:
    // 1. equal to the pivot
    // 2. smaller than the pivot
    // 3. unclassified
    // 4. greater than the pivot

    // pick pivot (TODO: randomize pivot?)
    iterator pivot = begin;
    std::cout << "picking pivot: " << *pivot << '\n';

    iterator first = next(begin);
    iterator last = end;

    while (first != last) {
        if (*first > *pivot) {
            --last;
            swap(*first, *last);
        } else if (*first < *pivot) {
            ++first;
        } else {
            ++pivot;
            swap(*pivot, *first);
            ++first;
            std::cout << "found duplicate of pivot\n";
        }
    }

    // look for duplicates in the elements smaller than the pivot
    auto res = partition_and_find_smallest_duplicate(next(pivot), first);
    if (res != first)
        return res;

    // if we have more than just one equal to the pivot, it is the smallest duplicate
    if (pivot != begin)
        return pivot;

    // neither, look for duplicates in the elements greater than the pivot
    return partition_and_find_smallest_duplicate(last, end);
}

template<typename container>
void remove_smallest_duplicate(container& c)
{
    using std::swap;
    auto it = partition_and_find_smallest_duplicate(c.begin(), c.end());
    if (it != c.end())
    {
        std::cout << "removing duplicate: " << *it << std::endl;

        // swap with the last last element before popping
        // to avoid copying the elements in between
        swap(*it, c.back());
        c.pop_back();
    }
}

int main()
{
    std::vector<int> data = {66, 3, 11, 7, 75, 62, 62, 52, 9, 24, 58, 72, 37, 2, 9, 28, 15, 58, 3, 60, 2, 14};

    remove_smallest_duplicate(data);
}

【讨论】:

    【解决方案4】:

    嗯,这是一个实际上删除最小的非唯一项(而不是仅仅打印它)的算法。

    template <typename Container>
    void
    removeSmallestNonunique(Container& c)
    {
        using value_type = typename Container::value_type;
        if (c.size() > 1)
        {
            std::make_heap(c.begin(), c.end(), std::greater<value_type>{});
            std::pop_heap(c.begin(), c.end(), std::greater<value_type>{});
            for (auto e = std::prev(c.end()); e != c.begin(); --e)
            {
                std::pop_heap(c.begin(), e, std::greater<value_type>{});
                if (*e == e[-1])
                {
                    c.erase(e);
                    break;
                }
            }
        }
    }
    

    我选择这个算法主要是因为Lightness Races in Orbit 没有。我不知道这是否会比sort/adjacent_find 快。答案几乎可以肯定取决于输入。

    例如如果没有重复,那么这个算法肯定比sort/adjacent_find慢。如果输入非常非常大,并且最小唯一性可能在排序范围的早期,则此算法可能比sort/adjacent_find 更快。

    我上面所说的一切都只是猜测。在对实际问题的统计上可能的输入执行所需的测量之前,我将退出。

    也许Omid 可以将其包含在他的测试中并提供一个摘要答案。 :-)

    7 小时后...计时

    我采用Omid's 代码,纠正了其中的一个小错误,纠正了其他两种算法以实际擦除元素,并更改了测试工具以更广泛地改变大小和重复的数量。

    这是我在 -O3 使用 clang/libc++ 测试的代码:

    #include <unordered_map>
    #include <iostream>
    #include <vector>
    #include <algorithm>
    #include <random>
    #include <chrono>
    #include <cassert>
    
    template <typename Container>
    void
    erase_using_hashTable(Container& vec)
    {
        using T = typename Container::value_type;
        std::unordered_map<T, int> c;
        for (const auto& elem : vec){
            ++c[elem];
        }
        bool has = false;
        T min_;
        for (const auto& e : c)
        {
            if (e.second > 1)
            {
                min_ = has ? std::min(e.first, min_) : e.first;
                has = true;
            }
        }
        if (has)
            vec.erase(std::find(vec.begin(), vec.end(), min_));
    }
    
    template <typename Container>
    void 
    eraseSmallestNonunique(Container& c)
    {
       std::sort(std::begin(c), std::end(c));
       auto it = std::adjacent_find(std::begin(c), std::end(c));
    
       if (it != std::end(c))
           c.erase(it);
    }
    
    template <typename Container>
    void
    removeSmallestNonunique(Container& c)
    {
        using value_type = typename Container::value_type;
        if (c.size() > 1)
        {
            std::make_heap(c.begin(), c.end(), std::greater<value_type>{});
            std::pop_heap(c.begin(), c.end(), std::greater<value_type>{});
            for (auto e = std::prev(c.end()); e != c.begin(); --e)
            {
                std::pop_heap(c.begin(), e, std::greater<value_type>{});
                if (*e == e[-1])
                {
                    c.erase(e);
                    break;
                }
            }
        }
    }
    
    template<typename iterator>
    iterator partition_and_find_smallest_duplicate(iterator begin, iterator end)
    {
        using std::swap;
        if (begin == end)
            return end; // empty sequence
    
        // The range begin,end is split in four partitions:
        // 1. equal to the pivot
        // 2. smaller than the pivot
        // 3. unclassified
        // 4. greater than the pivot
    
        // pick pivot (TODO: randomize pivot?)
        iterator pivot = begin;
        iterator first = next(begin);
        iterator last = end;
    
        while (first != last) {
            if (*first > *pivot) {
                --last;
                swap(*first, *last);
            } else if (*first < *pivot) {
                ++first;
            } else {
                ++pivot;
                swap(*pivot, *first);
                ++first;
            }
        }
    
        // look for duplicates in the elements smaller than the pivot
        auto res = partition_and_find_smallest_duplicate(next(pivot), first);
        if (res != first)
            return res;
    
        // if we have more than just one equal to the pivot, it is the smallest duplicate
        if (pivot != begin)
            return pivot;
    
        // neither, look for duplicates in the elements greater than the pivot
        return partition_and_find_smallest_duplicate(last, end);
    }
    
    template<typename container>
    void remove_smallest_duplicate(container& c)
    {
        using std::swap;
        auto it = partition_and_find_smallest_duplicate(c.begin(), c.end());
        if (it != c.end())
        {
            swap(*it, c.back());
            c.pop_back();
        }
    }
    
    int  main()
    {
        const int MaxArraySize = 5000000;
        const int minArraySize = 5;
        const int numberOfTests = 3;
    
        //std::ofstream file;
        //file.open("test.txt");
        std::mt19937 generator;
    
        for (int t = minArraySize; t <= MaxArraySize; t *= 10)
        {
            const int range = 3*t/2;
            std::uniform_int_distribution<int> distribution(0,range);
    
            std::cout << "Array size = " << t << "  range = " << range << '\n';
    
            std::chrono::duration<double> avg{},avg2{}, avg3{}, avg4{};
            for (int n = 0; n < numberOfTests; n++)
            {
                std::vector<int> save_vec;
                save_vec.reserve(t);
                for (int i = 0; i < t; i++){//por kardan array ba anasor random
                    save_vec.push_back(distribution(generator));
                }
                //method1
                auto vec = save_vec;
                auto start = std::chrono::steady_clock::now();
                erase_using_hashTable(vec);
                auto end = std::chrono::steady_clock::now();
                avg += end - start;
                auto answer1 = vec;
                std::sort(answer1.begin(), answer1.end());
    
                //method2
                vec = save_vec;
                start = std::chrono::steady_clock::now();
                eraseSmallestNonunique(vec);
                end = std::chrono::steady_clock::now();
                avg2 += end - start;
                auto answer2 = vec;
                std::sort(answer2.begin(), answer2.end());
                assert(answer2 == answer1);
    
                //method3
                vec = save_vec;
                start = std::chrono::steady_clock::now();
                removeSmallestNonunique(vec);
                end = std::chrono::steady_clock::now();
                avg3 += end - start;
                auto answer3 = vec;
                std::sort(answer3.begin(), answer3.end());
                assert(answer3 == answer2);
    
                //method4
                vec = save_vec;
                start = std::chrono::steady_clock::now();
                remove_smallest_duplicate(vec);
                end = std::chrono::steady_clock::now();
                avg4 += end - start;
                auto answer4 = vec;
                std::sort(answer4.begin(), answer4.end());
                assert(answer4 == answer3);
            }
            //file << avg/numberOfTests <<" "<<avg2/numberOfTests<<'\n';
            //file << "__\n";
            std::cout <<   "Method1 : " << (avg  / numberOfTests).count() << 's'
                      << "\nMethod2 : " << (avg2 / numberOfTests).count() << 's'
                      << "\nMethod3 : " << (avg3 / numberOfTests).count() << 's'
                      << "\nMethod4 : " << (avg4 / numberOfTests).count() << 's'
                      << "\n\n";
        }
    
    }
    

    这是我的结果:

    Array size = 5  range = 7
    Method1 : 8.61967e-06s
    Method2 : 1.49667e-07s
    Method3 : 2.69e-07s
    Method4 : 2.47667e-07s
    
    Array size = 50  range = 75
    Method1 : 2.0749e-05s
    Method2 : 1.404e-06s
    Method3 : 9.23e-07s
    Method4 : 8.37e-07s
    
    Array size = 500  range = 750
    Method1 : 0.000163868s
    Method2 : 1.6899e-05s
    Method3 : 4.39767e-06s
    Method4 : 3.78733e-06s
    
    Array size = 5000  range = 7500
    Method1 : 0.00124788s
    Method2 : 0.000258637s
    Method3 : 3.32683e-05s
    Method4 : 4.70797e-05s
    
    Array size = 50000  range = 75000
    Method1 : 0.0131954s
    Method2 : 0.00344415s
    Method3 : 0.000346838s
    Method4 : 0.000183092s
    
    Array size = 500000  range = 750000
    Method1 : 0.25375s
    Method2 : 0.0400779s
    Method3 : 0.00331022s
    Method4 : 0.00343761s
    
    Array size = 5000000  range = 7500000
    Method1 : 3.82532s
    Method2 : 0.466848s
    Method3 : 0.0426554s
    Method4 : 0.0278986s
    

    更新

    我已经用Ulrich Eckhardt's algorithm 更新了上面的结果。他的算法相当具有竞争力。干得好乌尔里希!

    我应该警告这个答案的读者,Ulrich 的算法容易受到“快速排序 O(N^2) 问题”的影响,其中对于特定输入,算法可能会严重退化。通用算法是可修复的,Ulrich 显然知道该漏洞,正如以下评论所证明的那样:

    // pick pivot (TODO: randomize pivot?)
    

    这是对 O(N^2) 问题的一种防御,还有其他的,例如检测不合理的递归/迭代并在中途切换到另一种算法(例如方法 3 或方法 2)。正如所写的,方法 4 在给定有序序列时受到严重影响,而在给定反向序列时是灾难性的。在我的平台上,对于这些情况,方法 3 也不是方法 2 的最佳选择,尽管不如方法 4 差。

    为类似快速排序的算法找到解决 O(N^2) 问题的理想技术有点像黑魔法,但值得花时间。我肯定会认为方法 4 是工具箱中的一个有价值的工具。

    【讨论】:

    • 很好的回应!虽然我喜欢所有三种建议的方法,但我接受了这个,因为这似乎是最快的。 (为你们所有人+1)
    • @Howard Hinnant 很好的答案。不出所料,调整了我的代码以避免重新创建微小的对象(您似乎忽略了我对此的劝告),解决方案#1(我的)低于#2,但仍然高于您的。关于这个有一些有趣的事情,有些事情我不太明白。请让我知道我是否可以就此给您写信。
    • @AmiTavory:我的门是开着的。 :-) 如果您(或其他任何人)提供了更新的算法,我很乐意用最新结果更新这个答案。但是,我会坚持使用可编译的更新(与人类语言描述相反)。
    • 感谢您的邀请,@HowardHinnant!我只是冒昧地添加了一个实现下述算法的版本。有趣的是,我的结果并不是决定性的。它大约需要第三种算法的一半时间,有时似乎更接近更大的范围,但并非始终如此。
    • @HowardHinnant 请在下面查看我的新答案。我相信您的基准测试存在一些问题。
    【解决方案5】:

    (我添加了一个额外的答案,因为 1)第一个答案的重点是使用现成的 STL 组件,以及 2)Howard Hinnant 提出了一些有趣的观点。)

    感谢 Howard Hinnant 对不同方法进行基准测试的原则(以及非常独特的解决方案)!它导致了一些我个人觉得有趣(但并不完全理解)的东西。

    然而,基准测试的执行有些问题,恕我直言。

    问题说明问题是

    ...在这种情况下使用具有双重成员的对象...有效地执行此操作很重要,因为代码用于程序的瓶颈

    然而,测试:

    • ints进行了操作,这是基于排序机制的优势;虽然double 比较和哈希都比ints' 更昂贵,但比较次数为Theta(n log(n)),而哈希次数为O( n).

    • 获取了我的 main 函数的主体,并将其包装在一个函数(而不是类对象)中,并且没有使用池分配器。坦率地说,我认为这是一个使结果变得毫无意义的缺陷,因为它基本上确立了一个众所周知的事实,即动态分配 + 大型容器的不必要的重新初始化是昂贵的。

    • 依赖于排序算法可以只返回它们所操作的vector 的事实(对于原始问题,这无法完成)。在下文中,我让这一点滑落,因为doubles 的vector 的问题本身很有趣,但 OP 应该注意这可能会改变更多。


    所以,为了处理第二个问题,我最初使用了我自己的gcc libstdc++ pb_ds extension 中的基于探测的哈希表。这本身将解决方案#1 的运行时间减少到解决方案#2 (sort + adjacent_find) 的运行时间以下,但它仍然比#3 (make_heap) 更昂贵。

    为了进一步减少这种情况,我使用了似乎相关的最退化的“哈希表”形式。

    template<typename T, class Hash=std::hash<T>>
    class smallest_dup_remover
    {
    public:
        explicit smallest_dup_remover(std::size_t max_size) 
        {
            while(m_mask < max_size)
                m_mask *= 2;
    
            m_status.resize(m_mask);
            m_vals.resize(m_mask);
    
            --m_mask;
        }
    
        void operator()(std::vector<T> &vals)
        {
            std::fill(std::begin(m_status), std::end(m_status), 0);
            bool has = false;
            T min_;
            std::vector<T> spillover;
            spillover.reserve(vals.size());
            for(auto v: vals)
            {
                const std::size_t pos = m_hash(v) & m_mask;
                char &status = m_status[pos];
                switch(status)
                {
                case 0:
                    status = 1;
                    m_vals[pos] = v;
                    break;
                case 1:
                    if(m_vals[pos] == v)
                    {
                        status = 2;
                        min_ = has? std::min(min_, v): v;
                        has = true;
                    }
                    else
                        spillover.push_back(v);
                    break;
                case 2:
                   if(m_vals[pos] != v)
                        spillover.push_back(v);
                }
            }
            std::sort(std::begin(spillover), std::end(spillover));
            auto it = std::adjacent_find(std::begin(spillover), std::end(spillover));
            if(has && it == std::end(spillover))
                remove_min(vals, min_);
            else if(has && it != std::end(spillover))
                remove_min(vals, std::min(min_, *it));
            else if(!has && it != std::end(spillover))
                remove_min(vals, *it);
        }
    
    private:
        void remove_min(std::vector<T> &vals, T t)
        {
            vals.erase(std::find(vals.begin(), vals.end(), t)); 
        }
    
    private:
        size_t m_mask = 1;
        std::vector<char> m_status;
        std::vector<T> m_vals;
        Hash m_hash;
    };
    

    数据结构包含三个vectors:

    • 一个“状态”vector,包含 0、1 和“许多”的代码

    • 一个“值”vector,包含“散列值”

    • “溢出”向量,用于碰撞

    动态比较具有“许多”状态的对象以获得最小。碰撞对象(即与其他对象发生碰撞的对象)被推送到“溢出”vector。使用#2 中的方法检查溢出vector 的最低重复项。这与“许多”值中找到的最低值进行比较。


    Here 是重新测试此基准的基准代码,here 是生成以下图表的代码。

    提醒#1 是基于散列的,#2 是基于快速排序的,#3 是基于堆的。)

    从 Howard Hinnant 之前执行的测试开始(值是从值长度的 1.5 大小范围内随机生成的),结果如下:

    因此,他出色的基于堆的算法确实在这种情况下表现最佳,但它看起来与以前大不相同。特别是,基于哈希的算法在分析其内存分配时并没有那么糟糕。

    但是,假设我们将范围更改为完全随机的范围。然后结果变成这样:

    在这种情况下,基于哈希的解决方案效果最好,基于排序的解决方案次之,基于堆的解决方案效果最差。

    为了验证原因,这里还有两个测试。

    这是一个完全随机值 + 两个零值的测试(即最低重复项为零):

    最后,这里的所有值都是从 100 个可能的值(不管长度)生成的:

    发生的事情如下。基于堆的解决方案是三个中最依赖于分布的解决方案。 MakeHeap 算法是线性时间的,如果在此之后几乎立即遇到重复,则证明它是线性算法(但没有任何散列)。相反,采取另一个极端,根本没有重复。本质上,这个算法就变成了heapsort。堆排序比快速排序的劣势在理论上得到了很好的理解,在实践中也得到了很多验证。

    所以,基于堆的算法实际上是一个令人惊讶的好算法。虽然它确实有很大的差异,但在实践中可能会考虑避免它。


    一些观察:

    • 图表似乎没有意义:n log(n) 行为在哪里,至少对于解决方案 #2?

    • 为什么 Hinnant 测试与随机 + 低重复测试如此相似?使用 1.5 X 范围,鉴于这与 Bootstrap Resampling 非常相似,已知的重复结果约为 37%,我只是看不到它。

    • 正如 Howard Hinnant 所指出的,这实际上取决于分布。但是,情况与之前的基准相差甚远。

    • 一些实用点:

      • OP,您可能希望针对您的原始问题重新计时,使用真实分布和将原始结构向量两次复制到排序解决方案的开销。

        李>
      • 我想了很多关于如何并行化这个问题,但没有任何有趣的东西。这样做的一种方法(可能提出的问题多于答案)是在一个线程上运行 Howard Hinnant 的解决方案,在另一个线程上运行不同的解决方案,并使用找到的第一个结果。鉴于某些发行版的速度要快得多,而其他发行版的速度要慢得多,它可能会覆盖您的基础。

    【讨论】:

    • 不错的分析。看到 Ulrich 的方法在上面进行比较会很有趣。
    • @HowardHinnant 谢谢!刚刚注意到他有趣的解决方案。明天将其添加到替补席上。
    猜你喜欢
    • 2020-08-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-23
    • 2018-07-16
    • 1970-01-01
    • 1970-01-01
    • 2018-06-25
    相关资源
    最近更新 更多