整数的快速集合并集
我需要创建大量有序整数集的并集(我想避免重复,但如果有的话也没关系)。
这是迄今为止性能最佳的代码:
// some code added for better understanding
std::vector< std::pair<std::string, std::vector<unsigned int> > vec_map;
vec_map.push_back(std::make_pair("hi", std::vector<unsigned int>({1, 12, 1450});
vec_map.push_back(std::make_pair("stackoverflow", std::vector<unsigned int>({42, 1200, 14500});
std::vector<unsigned int> match(const std::string & token){
auto lower = std::lower_bound(vec_map.begin(), vec_map.end(), token, comp2());
auto upper = std::upper_bound(vec_map.begin(), vec_map.end(), token, comp());
std::vector<unsigned int> result;
for(; lower != upper; ++lower){
std::vector<unsigned int> other = lower->second;
result.insert(result.end(), other.begin(), other.end());
}
std::sort(result.begin(), result.end()); // This function eats 99% of my running time
return result;
}
valgrind(使用工具 callgrind)告诉我,我花了 99% 的时间进行排序。
这是我到目前为止尝试过的:
- 使用 std::set (性能非常差)
- 使用 std::set_union (性能差) 使用
- std::push_heap 维护堆(慢 50%)
是否有希望以某种方式获得一些性能?我可以更改我的容器并使用 boost,也许还有其他一些库(取决于其许可证)。
编辑整数可以大到 10 000 000 编辑2给出了一些我如何使用它的例子,因为有些混乱
I need to make lots of unions of ordered set of integers (I would like to avoid duplicates, but it is okay if there are).
This is the code with the best performance so far :
// some code added for better understanding
std::vector< std::pair<std::string, std::vector<unsigned int> > vec_map;
vec_map.push_back(std::make_pair("hi", std::vector<unsigned int>({1, 12, 1450});
vec_map.push_back(std::make_pair("stackoverflow", std::vector<unsigned int>({42, 1200, 14500});
std::vector<unsigned int> match(const std::string & token){
auto lower = std::lower_bound(vec_map.begin(), vec_map.end(), token, comp2());
auto upper = std::upper_bound(vec_map.begin(), vec_map.end(), token, comp());
std::vector<unsigned int> result;
for(; lower != upper; ++lower){
std::vector<unsigned int> other = lower->second;
result.insert(result.end(), other.begin(), other.end());
}
std::sort(result.begin(), result.end()); // This function eats 99% of my running time
return result;
}
valgrind (using the tool callgrind) tells me that I spend 99% of the time doing the sort.
This is what I tried so far :
- Using std::set (very bad performance)
- Using std::set_union (bad performance)
- maintaining a heap with std::push_heap (50% slower)
Is there any hope to gain somehow some performance? I can change my containers and use boost, and maybe some other lib (depending on its licence).
EDIT integers can be as big a 10 000 000
EDIT 2 gave some example of how I use it, because of some confusion
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
这看起来像一个多路合并的实例。根据输入(配置文件和时间!),最好的算法可能是您拥有的算法,或者通过从所有容器中选择最小整数或更复杂的算法来逐步构建结果的算法。
This looks like an instance of multi-way merge. Depending on the input (profile and time!), the best algorithm might be what you have or something that builds the result incrementally by selecting the smallest integer from all the containers or something more complex.
自定义合并排序可能提供一点帮助。
http://ideone.com/1rYTi
A custom merge sort may give a tiny amount of help.
http://ideone.com/1rYTi
替代解决方案:
方法 std::sort (如果它基于快速排序)非常适合对未排序的向量进行排序O(logN),对于排序的向量甚至更好,但是如果你的向量是倒置的排序的时间复杂度为 O(N^2)。
当您执行并集时,可能会出现很多操作数,其中第一个操作数包含比跟随者更大的值。
我会尝试以下操作(我想输入向量中的元素已经排序):
按照其他人的建议,您应该在开始填充结果向量之前在结果向量上重新保留所需的大小。
如果 std::distance(lower, upper) == 1 则没有理由合并,只需复制单个操作数的内容。
对联合的操作数进行排序,可能是按大小(较大的优先),或者如果范围不重叠或仅按第一个值部分重叠),以便最大化下一个已排序的元素数量步。也许最好的策略是同时考虑联合中每个操作数的大小和范围。很大程度上取决于实际数据。
对
如果每个操作数都包含很多元素,请继续将元素附加到结果向量的后面,但是在附加每个向量(从第二个开始)之后,您可以尝试合并(std::inplace_merge)旧向量 如果操作数很少,
如果操作数的数量很大(与元素总数相比),那么您应该保留之前的排序策略,但在排序后调用 std::unique 来消除重复。在这种情况下,您应该按包含的元素范围进行排序。
ALTERNATIVE SOLUTION:
Method std::sort (if it is based on quick-sort) is very good sorting non-sorted vectorsO(logN), is even better with sorted vector, but if your vector is inverted sorted have O(N^2).
It may happen that when you perform the union you have a lot of operands, with the first one containing bigger values than the followers.
I would try the following (I suppose elements from input vectors are already sorted):
As suggested by other people here you should reseve the required size on the results vector before start filling it.
In case std::distance(lower, upper) == 1 there is no reason to union, just copy the contento of the single operand.
Sort the operands of your union, maybe by size (bigger first), or if the ranges don't overlap or partially overlap just by first value), so that you maximize the number of elements that are already sorted on the next step. Probably the best is a strategy that keep in consideration both things SIZE and RANGE of every operand of your union. A lot depends on the real data.
If there are few operands with a lot of elements each, keep appending the elements on the back of the result vector, but after appending each vector (from the second) you can try to merge (std::inplace_merge) the old content with the appendend one, this also deduplicates elements for you.
If the number of operands is big (comparing to the total number of elements) then you should stay with your former strategy of sorting afterward but call std::unique to deduplicate after sort. In this case you should sort by the range of elements contained.
如果元素的数量是可能的
int
范围的相对较大的百分比,您可能会从本质上是简化的“散列连接”(使用数据库术语)。(如果与可能值的范围相比,整数的数量相对较少,这可能不是最好的方法。)
本质上,我们制作一个巨大的位图,然后仅在与该位图对应的索引上设置标志输入 int 并最终根据这些标志重建结果:
这
在我的机器上打印... ...。
如果您想从
std::vector
以外的其他地方获取输入int
,您可以实现自己的迭代器并将其传递给IntSetUnion
。If the number of elements is relatively large percentage of range of possible
int
s, you might get a decent performance out of what is essentially a simplified "hash join" (to use DB terminology).(If there is a relatively small number of integers compared to range of possible values, this is probably not the best approach.)
Essentially, we make a giant bitmap, then set flags only on indexes corresponding to the input
int
s and finally reconstruct the result based on these flags:This prints...
...on my machine.
If you want to get your input
int
s from something else other thanstd::vector<int>
, you can implement your own iterator and pass it toIntSetUnion
.您正在对有限范围内的整数进行排序,这是基数排序的罕见情况之一可以使用。不幸的是,知道这是否优于广义排序的唯一方法就是尝试一下。
You're sorting integers with a limited range, this is one of those rare circumstances where a radix sort could be used. Unfortunately the only way to know if this beats the generalized sort is to try it.