整数的快速集合并集
Fast set union of integer
我需要对有序的整数集进行大量的联合(我希望避免重复,但如果有的话也可以)。
这是目前为止性能最好的代码:
// 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,也许一些其他的库(取决于它的许可证)。
EDIT整数可以大到10 000 000EDIT 2给出了一些我如何使用它的例子,因为有些混乱
这看起来像是一个多路合并的实例。根据输入(配置文件和时间!),最好的算法可能是您所拥有的,或者是通过从所有容器中选择最小整数来增量构建结果的算法,或者是更复杂的算法。
自定义合并排序可能提供少量帮助。
#include <string>
#include <vector>
#include <algorithm>
#include <map>
#include <iostream>
#include <climits>
typedef std::multimap<std::string, std::vector<unsigned int> > vec_map_type;
vec_map_type vec_map;
struct comp {
bool operator()(const std::string& lhs, const std::pair<std::string, std::vector<unsigned int> >& rhs) const
{ return lhs < rhs.first; }
bool operator()(const std::pair<std::string, std::vector<unsigned int> >& lhs, const std::string& rhs) const
{ return lhs.first < rhs; }
};
typedef comp comp2;
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());
unsigned int num_vecs = std::distance(lower, upper);
typedef std::vector<unsigned int>::const_iterator iter_type;
std::vector<iter_type> curs;
curs.reserve(num_vecs);
std::vector<iter_type> ends;
ends.reserve(num_vecs);
std::vector<unsigned int> result;
unsigned int result_count = 0;
//keep track of current position and ends
for(; lower != upper; ++lower){
const std::vector<unsigned int> &other = lower->second;
curs.push_back(other.cbegin());
ends.push_back(other.cend());
result_count += other.size();
}
result.reserve(result_count);
//merge sort
unsigned int last = UINT_MAX;
if (result_count) {
while(true) {
//find which current position points to lowest number
unsigned int least=0;
for(unsigned int i=0; i< num_vecs; ++i ){
if (curs[i] != ends[i] && (curs[least]==ends[least] || *curs[i]<*curs[least]))
least = i;
}
if (curs[least] == ends[least])
break;
//push back lowest number and increase that vectors current position
if( *curs[least] != last || result.size()==0) {
last = *curs[least];
result.push_back(last);
}
++curs[least];
}
}
return result;
}
int main() {
vec_map.insert(vec_map_type::value_type("apple", std::vector<unsigned int>(10, 10)));
std::vector<unsigned int> t;
t.push_back(1); t.push_back(2); t.push_back(11); t.push_back(12);
vec_map.insert(vec_map_type::value_type("apple", t));
vec_map.insert(vec_map_type::value_type("apple", std::vector<unsigned int>()));
std::vector<unsigned int> res = match("apple");
for(unsigned int i=0; i<res.size(); ++i)
std::cout << res[i] << ' ';
return 0;
}
http://ideone.com/1rYTi 替代解决方案:
方法std::sort(如果它是基于快速排序)是非常好的排序非排序向量(logN),是更好的排序向量,但如果你的向量是倒排有O(N^2)。在执行联合操作时,可能会有很多操作数,其中第一个操作数的值比后面的操作数的值大。
我会尝试以下操作(我假设输入向量中的元素已经排序):
-
根据其他人的建议,在开始填充之前,您应该保留结果矢量所需的大小。
-
当std::distance(lower, upper) == 1时,无需进行并集,只需复制单操作数的内容即可。
-
对联合的操作数进行排序,可能是按大小排序(先大一点),或者如果范围不重叠(部分重叠只是按第一个值),以便在下一步中最大化已经排序的元素数量。最好的策略可能是同时考虑联合的每个操作数的SIZE和RANGE。
-
如果有几个操作数,每个操作数都有很多元素,继续在结果向量的后面追加元素,但是在追加每个向量(从第二个)之后,您可以尝试合并(std::inplace_merge)旧内容与追加的内容,这也会为您删除重复元素。
-
如果操作数的数量很大(与元素的总数相比),那么您应该继续使用之前的排序策略,但在排序后调用std::unique来重复数据删除。在这种情况下,您应该根据所包含的元素范围进行排序。
如果元素的数量在可能的int
s范围中所占的百分比相对较大,那么您可能会从本质上简化的"散列连接"(使用DB术语)中获得不错的性能。
(如果整数的数量相对于可能值的范围来说相对较少,这可能不是最好的方法。)
本质上,我们制作一个巨大的位图,然后只在与输入int
对应的索引上设置标志,最后基于这些标志重建结果:
#include <vector>
#include <algorithm>
#include <iostream>
#include <time.h>
template <typename ForwardIterator>
std::vector<int> IntSetUnion(
ForwardIterator begin1,
ForwardIterator end1,
ForwardIterator begin2,
ForwardIterator end2
) {
int min = std::numeric_limits<int>::max();
int max = std::numeric_limits<int>::min();
for (auto i = begin1; i != end1; ++i) {
min = std::min(*i, min);
max = std::max(*i, max);
}
for (auto i = begin2; i != end2; ++i) {
min = std::min(*i, min);
max = std::max(*i, max);
}
if (min < std::numeric_limits<int>::max() && max > std::numeric_limits<int>::min()) {
std::vector<int>::size_type result_size = 0;
std::vector<bool> bitmap(max - min + 1, false);
for (auto i = begin1; i != end1; ++i) {
const std::vector<bool>::size_type index = *i - min;
if (!bitmap[index]) {
++result_size;
bitmap[index] = true;
}
}
for (auto i = begin2; i != end2; ++i) {
const std::vector<bool>::size_type index = *i - min;
if (!bitmap[index]) {
++result_size;
bitmap[index] = true;
}
}
std::vector<int> result;
result.reserve(result_size);
for (std::vector<bool>::size_type index = 0; index != bitmap.size(); ++index)
if (bitmap[index])
result.push_back(index + min);
return result;
}
return std::vector<int>();
}
void main() {
// Basic sanity test...
{
std::vector<int> v1;
v1.push_back(2);
v1.push_back(2000);
v1.push_back(229013);
v1.push_back(-2243);
v1.push_back(-530);
std::vector<int> v2;
v1.push_back(2);
v2.push_back(243);
v2.push_back(90120);
v2.push_back(329013);
v2.push_back(-530);
auto result = IntSetUnion(v1.begin(), v1.end(), v2.begin(), v2.end());
for (auto i = result.begin(); i != result.end(); ++i)
std::cout << *i << std::endl;
}
// Benchmark...
{
const auto count = 10000000;
std::vector<int> v1(count);
std::vector<int> v2(count);
for (int i = 0; i != count; ++i) {
v1[i] = i;
v2[i] = i - count / 2;
}
std::random_shuffle(v1.begin(), v1.end());
std::random_shuffle(v2.begin(), v2.end());
auto start_time = clock();
auto result = IntSetUnion(v1.begin(), v1.end(), v2.begin(), v2.end());
auto end_time = clock();
std::cout << "Time: " << (((double)end_time - start_time) / CLOCKS_PER_SEC) << std::endl;
std::cout << "Union element count: " << result.size() << std::endl;
}
}
这打印…
Time: 0.402
…在我的机器上。
如果您想从std::vector<int>
以外的其他东西获得输入int
s,您可以实现自己的迭代器并将其传递给IntSetUnion
。
您对范围有限的整数进行排序,这是可以使用基数排序的极少数情况之一。不幸的是,要知道这是否优于广义排序,唯一的方法就是尝试一下。
- 处理多个异常集合的C++方法
- 给定n个元素的m个集合.在C++中找到出现在最大集合数中的元素
- 如何将ampl中的集合表示为c++中的向量
- 使用C++程序合并排序没有得到正确的输出
- 检查值是否在集合p1和p2中,但不在p3中
- 带过滤器的现代迭代c++集合
- 如何将结构插入到集合中并打印集合的成员
- 集合上的输出迭代器:assign和increment迭代器
- 如何将函数集合传递给客户端类,以便将它们当作客户端类本身的成员使用
- 实现一个在集合上迭代的模板函数
- 尝试使用集合函数时出现分段错误
- 互斥指针的集合
- c++找不到具有相同哈希的无序集合元素
- python集合的C++等价物是什么.计数器
- 用于合并排序的合并函数
- 如何在C++中递归地按相反顺序打印集合
- 将“Eigen::VectorXd”的集合合并为一个大的“Eigen::VectorXd”
- 合并C++中的集合,也会重载运算符
- 将多个集合元素合并为一个集合
- 将已排序的集合与映射合并