我在集合/地图中使用带有提示(insert
)的函数emplace_hint
。
api doc表示当使用提示位置时,它将开始从提示位置搜索最终位置,并在实际插入点位置或接近时显着加快处理速度它"
我想知道关闭这里是指之前,之后还是两者,以及如何有效地使用此功能?
如果使用lower_bound
或upper_bound
来找到附近的关闭位置,那么这个过程似乎不会加速。
答案 0 :(得分:4)
坏消息......
我们将这些类型称为map / set,但我们真正的意思是树< pair> / tree。树上的插入操作是lower_bound O(log(N)),然后是实际添加新值的操作。 (通常,树是RB树,因此添加可能涉及"旋转")
通过调用lower_bound然后插入,你只是简单地实现插入功能。没有办法可以更快。如果是,我们会问为什么这不是默认实现。
好消息......
如果您真正要求的是......"我如何比地图更快?#34;。这很简单。有几种选择,取决于什么是较慢 - 访问或插入/删除,存储了多少元素,键/值类型有多大,或者是否有特殊原因导致键/值移动成本高等等
1)哈希映射,在std中实现为unordered_map 这种技术有时称为桶分类。你所做的是提供一个将键转换为数字的功能。然后你修改数字并用它来查找一个地图数组。很多人误以为它意味着它提供线性访问,它不是,它仍然是O(log(N)),只是用较小的N. 缺点是它使用更多内存。显然哈希函数也需要很快。
2)
为了更快地查找,请考虑排序向量(有时称为平面图,请参阅boost :: flatmap)。它只是一个std :: vector< pair< key,value>>,只是排序。非常简单,这种结构比查找地图要快得多。它的工作原理是因为lower_bound函数也是一般算法,并且仍然是log(N),但是向量对缓存更友好。预计它会快4倍左右
插入/删除是O(N)因为你必须记忆内存....但是,如上所述,插入需要lower_bound调用或等效,并且这对于sorted-vectors更快。通过实验,我发现对于小于4K的结构,矢量比插入/删除的设置/映射更快,但之后变得更慢,但这显然是H / W依赖的。
3)不要插入/删除,使用上面排序的矢量,但不要调用insert / erase,而只需要插入push_back。要删除,请使用back()交换项目并调整大小。这意味着每次插入/擦除时排序都会中断,因此您需要一个脏标志。查找时,检查脏标志,如果设置了它,则排序。 std :: sort算法是真正的科学,它的速度非常快。我的意思是真的很快...唯一的缺点是它是N * log(N),所以对于大型数据集(1000个元素或更多元素),你需要多次插入/擦除以获得回报,尽管不是尽可能多的人怀疑。 它也变得足够复杂,你可能需要一个类/模板来抽象它。
快速插入/删除... 4)所有排序算法之王,是基数排序。使用基数排序实现3)可以为您提供O(N)排序时间。 在下侧,基数算法通常需要非常大的数据集,(通常> 10,000个元素),这就是它们通常不被使用的原因。对于较小的数据集,您通常会找到std:sort wins。 另外,我不知道标准的基数排序实现,所以它意味着编写自己的,或大量的谷歌搜索。通常,基数排序仅支持整数键(尽管没有固有的原因可以扩展它)。
黑客......
5)设置< float& ft;或map< float,X>意味着浮点比较。现在真正的hack是在浮点数据上使用整数比较(如alias-cast,在compare函数中将float *转换为int *)。这是有效的,因为浮点数将指数存储在MSB中,因此较大的数字具有较大的指数。唯一的问题是符号标志,它是错误的方式,所以要注意,如果你迭代结构,正值从低到高排序。负数从高到低排序,这意味着地图并未真正排序。如果您只有+ ve或-ve数字,这并不重要。如果您只插入/擦除/查找,那么绝对存储顺序并不重要
(如果你想知道为什么整数比较更快,它是因为它更复杂,通常CPU也有独立的浮点寄存器,对这些寄存器的访问速度较慢)。
另请注意,别名转换有时会破坏编译器优化,具体取决于您可能需要告诉编译器您正在做什么的编译器。您可以使用编译器标志执行此操作。在GCC上,这是no_strict_aliasing,另一方面,Visual Studio并不关心,并且应该没有问题)
...
等等,重点是有很多方法可以击败"标准"算法,因为标准算法涉及权衡利弊,以平衡所有情况。如果你只有一个已知的案例,你可以经常打败它们。但是......如果你不知道你存储的是什么,或者有多少,那么试图击败std :: XXXXX就是一个吸盘游戏。标准算法由几十年来一直这样做的人实施,并且知道他们在做什么。
这就是为什么你尝试的是愚蠢的,如果它很容易,std :: XXXX版本就已经可以了。
答案 1 :(得分:3)
这似乎与实现有关。所以你需要检查你的库实现。 (我没有检查其他修订版。)
n3797表102.§23.2.4。
一般情况下是对数,但如果是,则摊销常数 在p
之前插入
标准说如果你提供确切的插入点(例如由lower_bound给出),map / set将使用它,除非满足特殊情况,否则不需要做额外的工作来进行插入。如果您没有提供精确的插入点,它可以像常规插入一样运行。如果您提供的插入点不正确,则不允许其比常规插入更糟糕。
常规插入以与lower_bound
非常相似的方式在地图中查找插入点。 lower_bound
也是对数的。因此,如果您执行lower_bound + insert(hint, element)
,那么您的工作与insert(element)
完全相同。