Inplace union отсортированные векторы - PullRequest
4 голосов
/ 03 сентября 2010

Мне нужен эффективный метод для объединения inplace отсортированного вектора с другим отсортированным вектором. Я имею в виду, что алгоритм не должен создавать новый вектор или другое хранилище для хранения объединения, даже временно. Вместо этого первый вектор должен просто расти точно на количество новых элементов.

Что-то вроде:

void inplace_union(vector & A, const vector & B);

Где впоследствии A содержит все элементы A union B и отсортировано.

std::set_union в <algorithm> не будет работать, потому что он перезаписывает пункт назначения, который будет A .

Кроме того, это можно сделать одним проходом по двум векторам?

Редактировать: элементы, которые находятся в и A и B, должны появляться только один раз в A.

Ответы [ 3 ]

6 голосов
/ 03 сентября 2010

Полагаю, вы можете использовать алгоритм std::inplace_merge.Вот пример кода:

void inplace_union(std::vector<int>& a, const std::vector<int>& b)
{
    int mid = a.size(); //Store the end of first sorted range

    //First copy the second sorted range into the destination vector
    std::copy(b.begin(), b.end(), std::back_inserter(a));

    //Then perform the in place merge on the two sub-sorted ranges.
    std::inplace_merge(a.begin(), a.begin() + mid, a.end());

    //Remove duplicate elements from the sorted vector
    a.erase(std::unique(a.begin(), a.end()), a.end());
}
2 голосов
/ 03 сентября 2010

Да, это можно сделать на месте и за O ( n ), при условии, что оба входа отсортированы, и с одним проходом по обоим векторам.Вот как:

Расширить A (вектор назначения) на B.size() - освободить место для наших новых элементов.

Итерация назад по двум векторам, начиная с конца B и первоначальный конец A.Если векторы отсортированы по маленьким → большим (большим в конце), возьмите итератор, указывающий на большее число, и вставьте его в истинный конец A.Продолжайте, пока итератор B не достигнет начала B.Обратные итераторы должны быть особенно хороши здесь.

Пример:

A: [ 1, 2, 4, 9 ]
B: [ 3, 7, 11 ]

* = iterator, ^ = where we're inserting, _ = unitialized
A: [ 1, 3, 4, 9*, _, _, _^ ]   B: [ 3, 7, 11* ]
A: [ 1, 3, 4, 9*, _, _^, 11 ]  B: [ 3, 7*, 11 ]
A: [ 1, 3, 4*, 9, _^, 9, 11 ]  B: [ 3, 7*, 11 ]
A: [ 1, 3, 4*, 9^, 7, 9, 11 ]  B: [ 3*, 7, 11 ]
A: [ 1, 3*, 4^, 4, 7, 9, 11 ]  B: [ 3*, 7, 11 ]
A: [ 1, 3*^, 3, 4, 7, 9, 11 ]  B: [ 3, 7, 11 ]

Супер редактирование: Рассматривали ли вы std::inplace_merge?(что я, возможно, только что изобрел?)

0 голосов
/ 02 мая 2012

Идея set_difference хороша, но недостатком является то, что мы не знаем, насколько нам нужно заранее увеличить вектор.

Это мое решение, которое делает set_difference дважды, один раз.чтобы подсчитать количество дополнительных слотов, которые нам понадобятся, и еще раз сделать фактическую копию.

Примечание: это означает, что мы будем повторять исходный код дважды.

#include <algorithm>
#include <boost/function_output_iterator.hpp>

// for the test
#include <vector>
#include <iostream>


struct output_counter
{
   output_counter(size_t & r) : result(r) {}
   template <class T> void operator()(const T & x) const { ++result; }
private:
   size_t & result;
};


// Target is assumed to work the same as a vector
// Both target and source must be sorted
template <class Target, class It>
void inplace_union( Target & target, It src_begin, It src_end )
{
   const size_t mid = target.size(); // Store the end of first sorted range

   // first, count how many items we will copy
   size_t extra = 0;
   std::set_difference( 
         src_begin, src_end,
         target.begin(), target.end(),
         boost::make_function_output_iterator(output_counter(extra)));

   if (extra > 0) // don't waste time if nothing to do
   {
      // reserve the exact memory we will require
      target.reserve( target.size() + extra );

      // Copy the items from the source that are missing in the destination
      std::set_difference( 
            src_begin, src_end,
            target.begin(), target.end(),
            std::back_inserter(target) );

      // Then perform the in place merge on the two sub-sorted ranges.
      std::inplace_merge( target.begin(), target.begin() + mid, target.end() );
   }
}



int main()
{
   std::vector<int> a(3), b(3);
   a[0] = 1;
   a[1] = 3;
   a[2] = 5;

   b[0] = 4;
   b[1] = 5;
   b[2] = 6;

   inplace_union(a, b.begin(), b.end());

   for (size_t i = 0; i != a.size(); ++i)
      std::cout << a[i] << ", ";
   std::cout << std::endl;

   return 0;
}

Скомпилировано сзаголовки повышения, результат:

$ ./test 
1, 3, 4, 5, 6, 
...