【问题标题】:High-performance merging of ordered sets有序集的高性能合并
【发布时间】:2013-08-04 18:50:00
【问题描述】:

希望保持一组数字排序(升序或降序,但下面的示例仅显示升序)。最大速度的数据结构表示是个问题。

假设一个聚合程序不断地从许多不同的监控代理(例如通过网络)获取数字包。我们的想法是让它们始终快速分类。例如,您可能会按顺序获取这些数据包(使用整数,但实际情况是双精度):

A = [1, 3, 4, 6]
B = [1, 2, 3]
C = [2, 3, 5]
A = [2, 4, 7, 8]

等等。在第一个数据包之后,聚合器中的数据结构将已经排序(数据结构会记住排序中每个数字所指的来源):

[1, 3, 4, 6] => 事件

在下一个数据包之后,由于是新的来源,数据结构会是这样的

[1, 1, 2, 3, 3, 4, 6] => 事件

在下一个数据包之后,

[1, 1, 2, 2, 3, 3, 3, 4, 5, 6] => 事件

现在由于 A 发送了新数据包,我们必须找到 A 的旧值,并用新值替换它们,最后以新排序结束。替换和排序可以单独发生也可以不发生(就地),目标是极速:

[1, 2, 2, 2, 3, 3, 4, 5, 7, 8] => 事件

请注意,当您获得第二个 A 时,所有旧 As 都必须被新 As 数据包“替换”,同时保持排序。在每个数据包被排序到数据结构中后,它被复制并需要作为“事件”发送。这些数据包每隔几微秒就会在合并排序算法中疯狂而连续地到来。

* 最好的数据结构是什么?也许是 Splay 树或 AVL 树? *

【问题讨论】:

  • 如果您投反对票,请至少发表评论,以便 OP 改进问题。
  • 最快的算法是quantum bogosort
  • 每个数据包中的数字会一直排序吗?又名:您的示例 A、B、C 和 A 均按从低到高排序。 (如果是这样,你可以在你的排序中利用它)。
  • @CollinJSimpson double 满足 key 模板参数的所有要求,默认比较 std::less AFAIK。
  • 为什么最后需要排序?例如,什么是观察数据结构,我们可以利用哪些属性以及如何利用?有几个排序数组并给观察者一个花哨的迭代器有什么问题?

标签: performance algorithm sorting c++11 real-time-updates


【解决方案1】:

我猜这对于您的特定目的不会是最快的数据结构和算法,但它可能已经足够快了。自己测试一下。

请注意,std::forward_list 甚至 std::vector 可能会更快,具体取决于实际情况(-> 大 O 表示法中的常数因子)。

tmyklebu 提到了另一种方法in the comments:根据场景,按需合并可能会更快,例如单独存储所有数据集并将它们合并到 vector 以传递给事件处理程序,甚至使用“合并”迭代器(其增量获取单个数据集的下一个元素)。

使用自定义内存池 -> 自定义分配器可以进一步提高性能。

#include <set>
#include <iostream>
#include <iterator>
#include <algorithm>

// inserts a sorted range into the `to` container
template < typename To, typename InputIt >
void insert_new_sorted(To& to,
                       InputIt beg_old, InputIt end_old,
                       InputIt beg_new, InputIt end_new)
{
    auto const& comp = to.value_comp();
    typename To::iterator i = to.begin();

    // might improve performance: don't remove elements which are in both
    // ranges (old and new)
    while(beg_old != end_old && beg_new != end_new)
    {
        if(comp(*beg_old, *beg_new))
        {
            // remove old element
            i = to.find(*beg_old);  // "slow", no hint :(
            i = to.erase(i);
            ++beg_old;
        }else if(comp(*beg_new, *beg_old))
        {
            // insert new element
            // using the hint to achieve better performance
            i = to.insert(i, *beg_new);
            ++beg_new;
        }else
        {
            // both equal, do nothing
            ++beg_new;
            ++beg_old;
        }
    }

    // remove remaining old elements
    for(; beg_old != end_old; ++beg_old)
    {
        to.erase(to.find(*beg_old));  // "slow", no hint :(
    }

    // insert remaining new elements
    for(; beg_new != end_new; ++beg_new)
    {
        i = to.insert(i, *beg_new);
    }

    std::copy(to.begin(), to.end(),
        std::ostream_iterator<typename To::value_type>(std::cout, ", "));
    std::cout << std::endl;
}

int main()
{
    using set_t = std::multiset<double>;

    set_t const A = {1, 3, 4, 6};
    set_t const B = {1, 2, 3};
    set_t const C = {2, 3, 5};
    set_t const A2 = {2, 4, 7, 8};

    set_t result;
    insert_new_sorted(result, A.end(), A.end(), A.begin(), A.end());
    insert_new_sorted(result, B.end(), B.end(), B.begin(), B.end());
    insert_new_sorted(result, C.end(), C.end(), C.begin(), C.end());
    insert_new_sorted(result, A.begin(), A.end(), A2.begin(), A2.end());
}

输出:

1、3、4、6、
1, 1, 2, 3, 3, 4, 6,
1, 1, 2, 2, 3, 3, 3, 4, 5, 6,
1, 2, 2, 2, 3, 3, 4, 5, 7, 8,


另一种方法:存储插入元素的迭代器,以加快擦除速度。

【讨论】:

  • DyP,谢谢你的例子。问题是,A2 不是一个新源,它与 A 是同一个源。所以 A2 的值必须替换 A 的值——实际上,来自一个源的新数据包使其在排序集中的旧状态无效。见原帖。如果您按照数据包进行操作,您的列表应该与我给出的最终列表相同。
  • @user1676605 已修复,但在两点上没有达到高性能(即erasefind 都无法获得提示)。
  • DyP,谢谢。我将针对我的幼稚版本测试此版本并进行比较。如果您的版本至少没有我的两倍快,我会感到非常惊讶,尽管我的建议是 tmyklebu 提出的。完成后我会再次发布。更重要的是,我还学到了一些关于语言本身的有趣花絮。
  • @user1676605 注意 tmyklebu 的方法;如果您需要迭代每个“事件”的所有元素,那么该版本将比multiset 更快,尤其是如果您将各个集合存储为向量(具有快速迭代)。
  • DyP,Imo 你的版本复制较少。标准模板库喜欢复制(可能是为了保持它的功能和线程安全?但是当不需要复制时它会损害性能)但是没有测量​​我不会知道。
猜你喜欢
  • 2012-10-10
  • 2017-04-13
  • 1970-01-01
  • 1970-01-01
  • 2012-06-17
  • 1970-01-01
  • 2013-09-30
  • 2019-11-11
  • 1970-01-01
相关资源
最近更新 更多