快速设置整数并集

时间:2011-10-04 17:15:10

标签: 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>({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,也许还有其他一些lib(取决于它的许可证)。

编辑整数可以达到一千万 编辑2 给出了一些我如何使用它的例子,因为有些混乱

5 个答案:

答案 0 :(得分:2)

这看起来像是multi-way merge的一个实例。根据输入(配置文件和时间!),最佳算法可能是您拥有的或通过从所有容器中选择最小整数或更复杂的东西来逐步构建结果的东西。

答案 1 :(得分:1)

自定义合并排序可能提供少量帮助。

#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

答案 2 :(得分:1)

替代解决方案

方法std :: sort(如果它基于快速排序)是非常好的排序非排序向量O(logN),对于排序向量更好,但如果你的向量被反转排序有O(N ^ 2) )。 可能会发生这样的情况:当你执行联合时,你有很多操作数,第一个包含比追随者更大的值。

我会尝试以下(我猜输入向量中的元素已经排序):

  • 正如其他人在此建议的那样,您应该在开始填充之前在结果向量上重新设置所需的大小。

  • 如果std :: distance(lower,upper)== 1没有理由联合,只需复制单个操作数的contento。

  • 对联合的操作数进行排序,可能是按大小(大于第一个),或者如果范围不重叠或部分重叠仅仅是第一个值,那么您可以最大化元素的数量已经按下一步排序了。可能最好的策略是同时考虑你工会每个操作数的SIZE和RANGE。很大程度上取决于真实的数据。

  • 如果每个都有很多元素的操作数很少,请继续在结果向量的背面附加元素,但在追加每个向量(从第二个)后,您可以尝试合并(std :: inplace_merge) )包含appendend的旧内容,这也为你重复删除元素。

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

答案 3 :(得分:0)

如果元素的数量是可能的int范围的相对较大的百分比,您可能会从本质上简化的“散列连接”中获得不错的性能(使用数据库术语)。

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

基本上,我们创建一个巨型位图,然后仅在与输入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

...在我的机器上。

如果您希望从int以外的其他内容获取输入std::vector<int>,则可以实现自己的迭代器并将其传递给IntSetUnion

答案 4 :(得分:0)

您正在对有限范围内的整数进行排序,这是可以使用radix sort的极少数情况之一。不幸的是,知道这是否胜过广义排序的唯一方法就是尝试它。