C++ как объединить отсортированные векторы в отсортированный вектор / pop наименьший элемент из всех них?

у меня есть коллекция около ста или около того отсортированных 10K) из них (таким образом, векторы не обязательно имеют одинаковый размер).

что я хотел бы сделать, по существу, перебирать наименьшее и наибольшее целое число, которые содержатся во всех этих отсортированных векторах.

один из способов сделать это было бы объединить все эти сортированные векторы в сортированный вектор и просто повторять. Таким образом,

Вопрос 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 ответов


один из вариантов-это использовать std :: priority queue для поддержания кучи итераторов, где итераторы пузырятся кучи в зависимости от значений, на которые они указывают.

вы также можете рассмотреть возможность использования повторяющихся приложений std :: inplace_merge. Это потребует добавления всех данных вместе в большой вектор и запоминания смещений, при которых каждый отдельный отсортированный блок начинается и заканчивается, а затем передает их в inplace_merge. Это, вероятно, будет быстрее, чем решение кучи, хотя я думаю, что принципиально сложность эквивалентна.

обновление: я реализовал второй алгоритм, который я только что описал. Неоднократно делая слияние на месте. Этот код включен 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();
}

первое, что приходит на ум, - это сделать структуру кучи, содержащую итераторы для каждого вектора, упорядоченную по значению, на которое они в настоящее время указывают. (каждая запись также должна содержать конечный итератор)

текущий элемент находится в корне кучи, и для продвижения, вы просто либо поп его, либо увеличить его ключ. (последнее может быть сделано путем выскакивания, увеличения, а затем нажатия)

Я считаю, что это должно иметь асимптотическую сложность O(E log M) где E - общее количество элементов, и M - количество векторов.

если вы действительно выталкиваете все из векторов, вы можете сделать кучу указателей на свои векторы, вы можете рассматривать их как кучи, чтобы избежать штрафа за производительность стирания из передней части вектора. (или, вы можете скопировать все в deques первый)


объединение их всех вместе путем объединения пар за раз имеет одинаковую асимптотическую сложность, если ты осторожен с заказом. Если вы расположите все векторы в полном, сбалансированном двоичном дереве, а затем попарно слить, как вы идете вверх по дереву, то каждый элемент будет скопирован log M раз, также приводит к .

для дополнительной фактической эффективности вместо дерева вы должны повторно объединить наименьшие два вектора, пока у вас не останется только один. (опять же, размещение указателей на векторы в куче-это путь, но на этот раз упорядоченный длина)

(действительно, Вы хотите заказать "стоимость копирования" вместо длины. Дополнительная вещь для оптимизации для определенных типов значений)


если бы мне пришлось угадать, самым быстрым способом было бы использовать вторую идею, но с слиянием N-ary вместо попарного слияния для некоторого подходящего N (который, как я предполагаю, будет либо небольшой константой, либо примерно квадратным корнем из числа векторов), и выполнить слияние N-ary, используя первый алгоритм выше для перечисления содержимое N векторов сразу.


я использовал алгоритм, приведенный здесь, и сделал небольшую абстракцию; преобразование в шаблоны. Я закодировал эту версию в VS2010 и использовал лямбда-функцию вместо функтора. Я не знаю, в каком смысле это "лучше", чем предыдущая версия, но, может быть, это будет кому-то полезно?

#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 сортирует векторы, содержащие пары данных в соответствии с первым элементом данных. Надеюсь, кто-то сможет используйте это когда-нибудь :-)