C ++如何将已排序的向量合并到一个已排序的向量/弹出所有这些向量中的最小元素?

时间:2012-01-26 03:02:42

标签: c++ sorting vector mergesort processing-efficiency

我有大约一百个排序vector<int>的集合虽然大多数向量中包含少量整数,但是一些向量包含大量(> 10K)它们(因此矢量不一定具有相同的大小)。

我想要做的基本上是遍历从最小到最大的整数,这些整数包含在所有这些排序的向量中。

一种方法是将所有这些排序后的矢量合并到一个有序矢量&amp;简单地迭代。因此,

问题1:将排序后的矢量合并为有序矢量的最快方法是什么?

另一方面,我确信有更快/更聪明的方法来实现这一目标而不需要合并。重新排序整个事物 - 也许从这个排序向量集合中迭代地弹出最小的整数;没有合并它们..所以:

问题2:从一堆排序的vector<int>中弹出最少元素的禁区/最佳方法是什么?


根据下面的回复,以及对问题的评论,我实现了一种方法,我为排序的向量创建迭代器的优先级队列。我不确定这是否具有性能效率,但它似乎非常节省内存。我认为问题仍然存在,因为我不确定我们是否已经建立了最快的方式。

// compare vector pointers by integers pointed
struct cmp_seeds {
    bool operator () (const pair< vector<int>::iterator, vector<int>::iterator> p1, const pair< vector<int>::iterator, vector<int>::iterator> p2) const {
        return *(p1.first) >  *(p2.first);      
    }
};

int pq_heapsort_trial() {

    /* Set up the Sorted Vectors */ 
    int a1[] = { 2, 10, 100};
    int a2[] = { 5, 15, 90, 200};
    int a3[] = { 12 };

    vector<int> v1 (a1, a1 + sizeof(a1) / sizeof(int));
    vector<int> v2 (a2, a2 + sizeof(a2) / sizeof(int));
    vector<int> v3 (a3, a3 + sizeof(a3) / sizeof(int));

    vector< vector <int> * > sorted_vectors;
    sorted_vectors.push_back(&v1);
    sorted_vectors.push_back(&v2);
    sorted_vectors.push_back(&v3);
    /* the above simulates the "for" i have in my own code that gives me sorted vectors */

    pair< vector<int>::iterator, vector<int>::iterator> c_lead;
    cmp_seeds mycompare;

    priority_queue< pair< vector<int>::iterator, vector<int>::iterator>, vector<pair< vector<int>::iterator, vector<int>::iterator> >, cmp_seeds> cluster_feeder(mycompare);


    for (vector<vector <int> *>::iterator k = sorted_vectors.begin(); k != sorted_vectors.end(); ++k) {
        cluster_feeder.push( make_pair( (*k)->begin(), (*k)->end() ));
    }


    while ( cluster_feeder.empty() != true) {
        c_lead = cluster_feeder.top();
        cluster_feeder.pop();
        // sorted output
        cout << *(c_lead.first) << endl;

        c_lead.first++;
        if (c_lead.first != c_lead.second) {
            cluster_feeder.push(c_lead);
        }
    }

    return 0;
}

3 个答案:

答案 0 :(得分:4)

一种选择是使用std :: priority queue来维护一堆迭代器,迭代器根据它们指向的值冒泡堆。

您还可以考虑使用std :: inplace_merge的重复应用程序。这将涉及将所有数据一起附加到一个大向量中并记住每个不同的已排序块开始和结束的偏移量,然后将这些偏移量传递给inplace_merge。这可能比堆解决方案更快,但我认为从根本上说复杂性是等价的。

更新:我已经实现了我刚才描述的第二种算法。反复做一个mergesort到位。此代码位于ideone

这首先将所有已排序的列表连接成一个长列表。如果有三个源列表,这意味着有四个“偏移”,它们是完整列表中的四个点,元素在这四个点之间进行排序。然后,算法将同时拉出其中的三个,将两个相应的相邻排序列表合并为一个排序列表,然后记住要在new_offsets中使用的三个偏移中的两个。

这在一个循环中重复,将成对的相邻排序范围合并在一起,直到只剩下一个排序范围。

最终,我认为最好的算法将首先将最短的相邻范围对合并在一起。

// http://stackoverflow.com/questions/9013485/c-how-to-merge-sorted-vectors-into-a-sorted-vector-pop-the-least-element-fro/9048857#9048857
#include <iostream>
#include <vector>
#include <algorithm>
#include <cassert>
using namespace std;

template<typename T, size_t N>
vector<T> array_to_vector( T(*array)[N] ) { // Yes, this works. By passing in the *address* of
                                            // the array, all the type information, including the
                                            // length of the array, is known at compiler. 
        vector<T> v( *array, &((*array)[N]));
        return v;
}   

void merge_sort_many_vectors() {

    /* Set up the Sorted Vectors */ 
    int a1[] = { 2, 10, 100};
    int a2[] = { 5, 15, 90, 200};
    int a3[] = { 12 };

    vector<int> v1  = array_to_vector(&a1);
    vector<int> v2  = array_to_vector(&a2);
    vector<int> v3  = array_to_vector(&a3);


    vector<int> full_vector;
    vector<size_t> offsets;
    offsets.push_back(0);

    full_vector.insert(full_vector.end(), v1.begin(), v1.end());
    offsets.push_back(full_vector.size());
    full_vector.insert(full_vector.end(), v2.begin(), v2.end());
    offsets.push_back(full_vector.size());
    full_vector.insert(full_vector.end(), v3.begin(), v3.end());
    offsets.push_back(full_vector.size());

    assert(full_vector.size() == v1.size() + v2.size() + v3.size());

    cout << "before:\t";
    for(vector<int>::const_iterator v = full_vector.begin(); v != full_vector.end(); ++v) {
            cout << ", " << *v;
    }       
    cout << endl;
    while(offsets.size()>2) {
            assert(offsets.back() == full_vector.size());
            assert(offsets.front() == 0);
            vector<size_t> new_offsets;
            size_t x = 0;
            while(x+2 < offsets.size()) {
                    // mergesort (offsets[x],offsets[x+1]) and (offsets[x+1],offsets[x+2])
                    inplace_merge(&full_vector.at(offsets.at(x))
                                 ,&full_vector.at(offsets.at(x+1))
                                 ,&(full_vector[offsets.at(x+2)]) // this *might* be at the end
                                 );
                    // now they are sorted, we just put offsets[x] and offsets[x+2] into the new offsets.
                    // offsets[x+1] is not relevant any more
                    new_offsets.push_back(offsets.at(x));
                    new_offsets.push_back(offsets.at(x+2));
                    x += 2;
            }
            // if the number of offsets was odd, there might be a dangling offset
            // which we must remember to include in the new_offsets
            if(x+2==offsets.size()) {
                    new_offsets.push_back(offsets.at(x+1));
            }
            // assert(new_offsets.front() == 0);
            assert(new_offsets.back() == full_vector.size());
            offsets.swap(new_offsets);

    }
    cout << "after: \t";
    for(vector<int>::const_iterator v = full_vector.begin(); v != full_vector.end(); ++v) {
            cout << ", " << *v;
    }
    cout << endl;
}

int main() {
        merge_sort_many_vectors();
}

答案 1 :(得分:2)

首先想到的是创建一个包含迭代器的堆结构到每个向量,按它们当前指向的值排序。 (当然,每个条目也需要包含结束迭代器)

当前元素位于堆的根目录,要进步,只需弹出它或增加其键即可。 (后者可以通过弹出,递增,然后推动来完成)

我认为这应该具有渐近复杂度O(E log M),其中E是元素的总数,M是向量的数量。

如果你真的从向量中弹出一切,你可以制作一堆指向你的向量的指针,你可能也希望将它们视为堆,以避免从向量前面擦除的性能损失。 (或者,您可以先将所有内容复制到deque


如果你对订单有所了解,那么通过一次合并对将它们合并在一起具有相同的渐近复杂性。如果您将所有向量排列在一个完整的平衡二叉树中,那么在向上树时成对合并,那么每个元素将被复制log M次,同时也会导致O(E log M)算法。

为了获得额外的实际效率,您应该重复合并最小的两个向量,而不是树,直到您只剩下一个向量。 (再次,将指针放在堆中的向量是要走的路,但这次按长度排序)

(实际上,您希望按“复制成本”而不是长度进行排序。针对特定值类型进行优化的额外事项)


如果我不得不猜测,最快的方法是使用第二个想法,但是使用N-ary合并而不是成对合并,对于一些合适的N(我猜测它将是一个小常数,或者大致是矢量数的平方根),并使用上面的第一个算法执行N-ary合并,一次枚举N个矢量的内容。

答案 2 :(得分:0)

我已经使用了这里给出的算法并进行了一些抽象;转换为模板。我在VS2010中编写了这个版本并使用了lambda函数而不是functor。我不知道这在任何意义上是否更好&#39;比以前的版本,但也许它会有用吗?

#include <queue>
#include <vector>

namespace priority_queue_sort
{
    using std::priority_queue;
    using std::pair;
    using std::make_pair;
    using std::vector;

    template<typename T>
    void value_vectors(const vector< vector <T> * >& input_sorted_vectors, vector<T> &output_vector)
    {
        typedef vector<T>::iterator iter;
        typedef pair<iter, iter>    iter_pair;

        static auto greater_than_lambda = [](const iter_pair& p1, const iter_pair& p2) -> bool { return *(p1.first) >  *(p2.first); };

        priority_queue<iter_pair, std::vector<iter_pair>, decltype(greater_than_lambda) > cluster_feeder(greater_than_lambda);

        size_t total_size(0);

        for (auto k = input_sorted_vectors.begin(); k != input_sorted_vectors.end(); ++k)
        {
            cluster_feeder.push( make_pair( (*k)->begin(), (*k)->end() ) );
            total_size += (*k)->size();
        }

        output_vector.resize(total_size);
        total_size = 0;
        iter_pair c_lead;
        while (cluster_feeder.empty() != true)
        {
            c_lead = cluster_feeder.top();
            cluster_feeder.pop();
            output_vector[total_size++] = *(c_lead.first);
            c_lead.first++;
            if (c_lead.first != c_lead.second) cluster_feeder.push(c_lead);
        }
    }

    template<typename U, typename V>
    void pair_vectors(const vector< vector < pair<U, V> > * >& input_sorted_vectors, vector< pair<U, V> > &output_vector)
    {
        typedef vector< pair<U, V> >::iterator iter;
        typedef pair<iter, iter> iter_pair;

        static auto greater_than_lambda = [](const iter_pair& p1, const iter_pair& p2) -> bool { return *(p1.first) >  *(p2.first); };

        priority_queue<iter_pair, std::vector<iter_pair>, decltype(greater_than_lambda) > cluster_feeder(greater_than_lambda);

        size_t total_size(0);

        for (auto k = input_sorted_vectors.begin(); k != input_sorted_vectors.end(); ++k)
        {
            cluster_feeder.push( make_pair( (*k)->begin(), (*k)->end() ) );
            total_size += (*k)->size();
        }

        output_vector.resize(total_size);
        total_size = 0;
        iter_pair c_lead;

        while (cluster_feeder.empty() != true)
        {
            c_lead = cluster_feeder.top();
            cluster_feeder.pop();
            output_vector[total_size++] = *(c_lead.first);  
            c_lead.first++;
            if (c_lead.first != c_lead.second) cluster_feeder.push(c_lead);
        }
    }
}

算法priority_queue_sort::value_vectors仅对包含值的向量进行排序;而priority_queue_sort::pair_vectors根据第一个数据元素对包含数据对的向量进行排序。希望有一天有人可以使用这个: - )