C++ 整数的快速集并

C++ 整数的快速集并,c++,set,std,C++,Set,Std,我需要对有序的整数集进行大量的并集(我希望避免重复,但如果有也可以) 这是迄今为止性能最好的代码: // 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&g

我需要对有序的整数集进行大量的并集(我希望避免重复,但如果有也可以)

这是迄今为止性能最好的代码:

// 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;
}
//为了更好地理解,添加了一些代码
std::vector秒;
result.insert(result.end(),other.begin(),other.end());
}
std::sort(result.begin(),result.end());//此函数占用我99%的运行时间
返回结果;
}
valgrind(使用callgrind工具)告诉我,我99%的时间都花在排序上

这就是我迄今为止所尝试的:

  • 使用std::set(性能非常差)
  • 使用std::set_union(性能差)
  • 使用std::push_堆维护堆(速度慢50%)
是否有希望以某种方式获得一些性能?我可以更改容器并使用boost,或者使用其他库(取决于其许可证)

编辑整数可以大到10000
编辑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;
    }
#包括
#包括

#include

如果元素的数量在可能的
int
s范围内的百分比相对较大,那么从本质上简化的“哈希连接”(使用DB术语)中,您可能会获得相当好的性能

(如果与可能值的范围相比,整数的数量相对较少,则这可能不是最佳方法。)

本质上,我们制作一个巨大的位图,然后只在与输入
int
s相对应的索引上设置标志,最后根据这些标志重建结果:

#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;

    }

}
…在我的机器上


如果您想从
std::vector
以外的其他地方获取输入
int
s,您可以实现自己的迭代器,并将其传递给
IntSetUnion
您正在对范围有限的整数进行排序,这是可以使用a的极少数情况之一。不幸的是,唯一知道这是否优于一般的排序是尝试一下。

替代解决方案

方法std::sort(如果它是基于快速排序)是非常好的排序非排序向量(logN),使用排序向量更好,但如果向量是反向排序的,则有O(N^2)。 执行并集时,可能会有很多操作数,第一个操作数包含的值比后面的操作数大

我将尝试以下方法(我假设输入向量中的元素已经排序):

  • 正如这里的其他人所建议的,在开始填充之前,您应该在结果向量上重新保存所需的大小

  • 如果std::distance(lower,upper)==1,则没有理由进行并集,只需复制单个操作数的contento即可

  • 对并集的操作数排序,可以按大小排序(先大一点),或者如果范围不重叠或部分重叠,则只按第一个值排序),以便最大化下一步已排序的元素数。最好的策略可能是同时考虑联合中每个操作数的大小和范围。这在很大程度上取决于实际数据

  • 如果有几个操作数,每个操作数都有很多元素,请继续将元素追加到结果向量的后面,但在追加每个向量(从第二个向量开始)后,可以尝试将旧内容与追加内容合并(std::inplace_merge),这也会为您消除重复元素

  • 如果操作数较大(与元素总数相比),则应继续使用之前的排序策略,但在排序后调用std::unique进行重复数据消除。在这种情况下,应按包含的元素范围进行排序


(1)为什么
下限
上限
有不同的比较?可能是一个bug?(2)在放入任何内容之前对结果使用
保留
。节省了大量的钱。(3)将
其他
作为向量的引用。节省了大量的钱。(4)为什么要排序?整数已经排序了(或者你不能使用
下限
)。你不需要再排序了。@TristramGräbener:我刚刚意识到vecäu映射是一个整数向量的映射,而不是整数的映射。因此(几个人的)混乱。
reserve
和引用没有什么区别?这很令人惊讶。好的编译器。猜测:a而不是插入结果,可能会有区别。应该保持结果排序。@AlfP.Steinbach:嗯,如果
vec\u map
std::map
类型,那就好了。@OP:如果您提供了一些示例输入和预期输出。您说
结果
少于1000个元素?我很难相信
std::sort
需要2920ms才能排序少于1000个元素。您的问题出在其他地方。
结果是否会返回(**至少)
需要在找到指向最低数字的当前位置的循环之后?是的。我在一个点上更改了缩进,显然错误地理解了属于哪个循环的内容。自从我了解了
结果\u计数之后
Time: 0.402