使用提示将已排序的范围插入到std :: set中

时间:2018-04-12 14:47:40

标签: c++ c++11 stdset

假设我有std::set(按定义排序),我有另一系列排序元素(为简单起见,在另一个std::set宾语)。另外,我保证第二组中的所有值都大于第一组中的所有值。

我知道我可以有效地将一个元素插入std::set - 如果我传递了正确的hint,那么这将是 O(1)。我知道我可以在std::set中插入任何范围,但由于没有传递hint,这将是 O(k logN)(其中k是新元素的数量,并且N个旧元素)。

我可以在std::set中插入一个范围并提供hint吗?我能想到的唯一方法是使用{{1}进行单个插入},这确实将我案例中插入操作的复杂性降低到 O(k)

hint

3 个答案:

答案 0 :(得分:3)

首先,要进行您正在谈论的合并,您可能想要使用set(或map' s){ {1}}成员函数,可以将现有的merge合并到此函数中。这样做的好处(以及您可能不希望的原因,取决于您的使用模式)是合并的项目实际上是从一个集合移动到另一个集合,因此您不必分配新节点(这可以节省相当多的时间)。缺点是节点然后从源集中消失,因此如果您需要将每个局部直方图合并到全局直方图后保持不变,那么您不想这样做。

在搜索已排序的向量时,通常可以比O(log N)做得更好。假设合理可预测的分布,您可以使用插值搜索(通常)在O(log log N)附近进行搜索,通常称为"伪常数"复杂性。

鉴于您不经常插入相对,您可能还会考虑混合结构。这从一小部分数据开始,您不会对其进行排序。当您达到其大小的上限时,您将其排序并将其插入到已排序的向量中。然后,您返回到未分类区域添加项目。当它达到限制时,再次对其进行排序并将其与现有的排序数据合并。

假设您将未排序的块限制为不大于log(N),搜索复杂度仍为O(log N) - 一个log(n)二进制搜索或log log N对排序的块进行内插搜索,并且一个日志(n)对未排序的块进行线性搜索。一旦您确认某个项目尚未存在,添加它就会产生持续的复杂性(只需将其添加到未排序的块的末尾)。最大的优点是,它仍然可以轻松地使用连续的结构,例如矢量,因此它比典型的树结构更加缓存友好。

由于您的全局直方图(显然)只填充了来自本地直方图的数据,因此可能值得考虑将其保留在向量中,并且当您需要合并来自其中一个本地块的数据时,只需使用map获取现有的全局直方图和局部直方图,并将它们合并为一个新的全局直方图。这具有O(N + M)复杂度(N =全局直方图的大小,M =局部直方图的大小)。根据局部直方图的典型大小,这可能很容易成为一种胜利。

答案 1 :(得分:1)

您可以使用特殊功能更有效地合并集合。

如果您坚持,insert会返回有关插入位置的信息。

iterator insert( const_iterator hint, const value_type& value );

代码:

std::set <int> bigSet{1,2,5,7,10,15,18};
std::set <int> biggerSet{50,60,70};  

auto hint = bigSet.cend();
for(auto& bigElem : biggerSet)
    hint = bigSet.insert(hint, bigElem);

当然,这假设您要插入的新元素将在最终集合中结束或关闭。否则没有太大的收获,只有因为源是set(它是有序的),所以三个中的大约一半将不会被查找。

还有一个成员函数 template< class InputIt > void insert( InputIt first, InputIt last );。 这可能会或可能不会在内部做这样的事情。

答案 2 :(得分:1)

合并两个已排序的容器比排序快得多。它的复杂性是O(N),所以在理论上你所说的是有道理的。这就是merge-sort是最快排序算法之一的原因。如果你按照链接,你也会发现伪代码,你正在做的只是主循环的一次传递 您还会发现STL中实现的算法为std::merge。这需要任何容器作为输入,我建议使用std :: vector作为新元素的默认容器。对矢量进行排序是一种非常快速的操作。您甚至可能会发现使用排序向量而不是输出集更好。您始终可以使用std::lower_bound从排序向量中获取O(Nlog(N))性能 与集合/映射相比,向量具有许多优点。其中最重要的是它们很容易在调试器中可视化:-)

(std :: merge底部的代码显示了使用向量的示例)