Как использовать сортировку слиянием для удаления дубликатов? - PullRequest
3 голосов
/ 15 ноября 2009

Я использую рекурсивную сортировку слиянием для сортировки списка ссылок, но во время сортировки слиянием я хотел бы удалить дубликаты. У кого-нибудь есть понимание того, как этого достичь?

Я использую код С.

Ответы [ 6 ]

8 голосов
/ 16 ноября 2009

При сортировке слиянием вы берете два (или более) уже отсортированных списка, несколько раз применяя следующие правила:

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

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

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

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

7 голосов
/ 15 ноября 2009

Чтобы использовать сортировку слиянием для удаления дубликатов, вы должны игнорировать элементы, которые повторяются в процессе слияния.

1 голос
/ 28 октября 2013

Используя C ++, но вы можете просто использовать массивы вместо векторов для C

#include <iostream>
#include <vector>

// merge 2 arrays using a temp array
void merge (std::vector<int>& v, std::vector<int>& tmpArray, int left, int center, int right ) 
{
  int leftPos = left;
  int leftEnd = center;

  int tmpPos = leftPos;

  int rightEnd = right;
  int rightPos = center + 1;

  // finger matching algo left and right
  while ( leftPos <= leftEnd && rightPos <= rightEnd )
  {
     // this first if block here for equals is what does your duplicate removal magic
    if ( v[leftPos] == v[rightPos] )
    {
      tmpArray[tmpPos++] = std::move(v[leftPos++]);
      ++rightPos;
    }
    else if ( v[leftPos] < v[rightPos] )
      tmpArray[tmpPos++] = std::move(v[leftPos++]);
    else
      tmpArray[tmpPos++] = std::move(v[rightPos++]);
  }

  // copy rest of left
  while ( leftPos <= leftEnd )
    tmpArray[tmpPos++] = std::move(v[leftPos++]);

  // copy rest of right
  while ( rightPos <= rightEnd )
    tmpArray[tmpPos++] = std::move(v[rightPos++]);

  // copy tmp array back to array
  int numElements = right - left  + 1;
  for ( int i = 0; i < numElements; ++i, --rightEnd)
    v[rightEnd]=std::move(tmpArray[rightEnd]);
}

void mergeSort ( std::vector<int>& v, std::vector<int>& tmpArray, int left, int right )
{
  if ( left < right )
  {
    auto center = left + (right - left)/2;
    mergeSort(v, tmpArray, left, center);
    mergeSort(v, tmpArray, center+1, right);
    merge ( v, tmpArray, left, center, right );
  }
}

void mergeSort (std::vector<int>& v)
{
  int sz = v.size();
  std::vector<int> tmpArray ( sz, 0 );
  mergeSort (v, tmpArray, 0, sz-1);
}

int main ()
{
  std::vector<int> v { 7,8,6,5,4,3,9,12,14,17,21,1,-2,-3,-3,-3,-9,10,11 };
  mergeSort ( v );
  for ( auto&i : v)
    std::cout << i << " " ;
  std::cout << std::endl;
}
1 голос
/ 16 ноября 2009

Обновление моего оригинального ответа ниже с помощью более общего кода с использованием итераторов коллекций, а не только векторов.

// merge a sort collection
template<typename CollectionT>
void mergeCollection(CollectionT & collection, CollectionT & tmpCollection,
        typename CollectionT::iterator first, typename CollectionT::iterator mid,
        typename CollectionT::iterator last) {
    using IteratorType = typename CollectionT::iterator;

    IteratorType left = first;
    IteratorType  leftEnd = mid;

    IteratorType temp = tmpCollection.begin();
    auto const distance = std::distance(collection.begin(), first);
    std::advance(temp, distance);

    IteratorType right = mid;
    IteratorType rightEnd = last;

    // finger matching algo left and right
    while (left != leftEnd && right != rightEnd) {
        // this first if block here for equals is what does your duplicate removal magic
        if (*left == *right) {
            *temp++ = *left++;
            *temp++ = *right++;  // ++right for non-duplicate
        }
        else if (*left < *right) {
            *temp++ = *left++;
        }
        else {
            *temp++ = *right++;
        }
    }

    // copy rest of left
    while (left != leftEnd) {
        *temp++ = *left++;
    }

    // copy rest of right
    while (right != rightEnd) {
        *temp++ = *right++;
    }

    collection = tmpCollection;
}

template<typename CollectionT>
void mergeSortCollection(CollectionT & collection, CollectionT & tmpCollection, typename CollectionT::iterator first, typename CollectionT::iterator last) {

    auto const distance = std::distance(first, last);
    if(distance > 1) {
        // get mid iterator
        auto mid = first;
        std::advance(mid, distance / 2);
        mergeSortCollection(collection, tmpCollection, first, mid);
        mergeSortCollection(collection, tmpCollection, mid, last);
        mergeCollection(collection, tmpCollection, first, mid, last);
    }
}

template<typename CollectionT>
void mergeSortCollection(CollectionT & collection) {
    CollectionT tmpCollection {collection};
    mergeSortCollection(collection, tmpCollection, collection.begin(), collection.end());
}

}

тестовый код:

namespace { 
    template<typename It>
    auto printCollection =
            [](std::ostream& out, It const begin, It const end, std::string const & message = "") {
                using ValueType = typename std::iterator_traits<It>::value_type;
                out << message;
                std::copy(begin, end, std::ostream_iterator<ValueType>(out, ", "));
                out << std::endl;
            };
}

    TEST(Sort, MergeSortCollectionVector) {
        std::vector<int32_t> before = { 83, 86, 77, 15, 93, 35, 86, 92, 49, 21 };
        std::vector<int32_t> original = before;
        std::vector<int32_t> after = { 15, 21, 35, 49, 77, 83, 86, 86, 92, 93 };

        printCollection<decltype(before.begin())>(std::cout, before.begin(), before.end(), "BEFORE sort: ");
        mergeSortCollection(before);

        printCollection<decltype(before.begin())>(std::cout, before.begin(), before.end(), "AFTER sort: ");
        EXPECT_EQ(after, before);
        EXPECT_NE(original, before);
    }

    TEST(Sort, MergeSortCollectionList) {
        std::list<int32_t> before = { 83, 86, 77, 15, 93, 35, 86, 92, 49, 21 };
        std::list<int32_t> original = before;
        std::list<int32_t> after = { 15, 21, 35, 49, 77, 83, 86, 86, 92, 93 };

        printCollection<decltype(before.begin())>(std::cout, before.begin(), before.end(), "BEFORE sort: ");
        mergeSortCollection(before);

        printCollection<decltype(before.begin())>(std::cout, before.begin(), before.end(), "AFTER sort: ");
        EXPECT_EQ(after, before);
        EXPECT_NE(original, before);
    }

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

function merge(left,right)
var list result
while length(left) > 0 and length(right) > 0
    if first(left) < first(right)    // <--- change from <= to <
        append first(left) to result
        left = rest(left)
    else if first(left) > first(right)
        append first(right) to result
        right = rest(right)
    else        // <----- added case to remove duplicated items
        append first(right) to result
        left = rest(left)
        right = rest(right)
    end

end while
if length(left) > 0 
    append left to result
else  
    append right to result
return result
1 голос
/ 16 ноября 2009

Рассмотрим функцию слияния внутри сортировки слияния.

В процессе слияния вы, конечно, сравниваете элементы друг с другом.

Убедитесь, что, если вы объединяете 2 отсортированных списка A и B, и если оба списка содержат одинаковое значение x, то случится так, что два идентичных элемента будут сравниваться друг с другом , Если вам нужны доказательства, мой подход будет состоять в том, чтобы показать, что если есть случай, когда два идентичных элемента сравниваются , а не , то один или оба списка фактически не отсортированы. Доказательство от противного, детка!

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

Далее, убедитесь, что если в двух списках есть два идентичных элемента , а не , которые объединяются только сейчас, то в конечном итоге они будут объединены вместе, и идентичные элементы будут обнаружены. Это в основном доказательство по индукции - если не сказать ничего другого, ясно, что самое последнее слияние (объединение отсортированных списков длины n / 2 и n / 2 в окончательный список длины n) объединит эти элементы.

Наконец, убедите себя, что не может существовать единственного списка с двумя одинаковыми элементами, если вы вернетесь к случаю n = 1 или n = 0. Это опять-таки индуктивный вывод, потому что, конечно, любой большой список сначала должен пережить процесс «фильтрации», описанный в первом большом абзаце.

Если вы убеждены в этих трех вещах, тогда будет очевидно, что решения Стивена или Тима вполне подходят.

0 голосов
/ 16 ноября 2009

Или просто используйте любую сортировку и, когда она завершится, отсканируйте отсортированный список и удалите дублированные элементы (они, естественно, будут рядом друг с другом)

...