Как выполнить сортировку на месте с помощью алгоритма сортировки слиянием?

Я знаю, что вопрос не слишком конкретный.

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

Все, что я могу найти (в сети), - это страницы, на которых написано «это слишком сложно» или «выходит за рамки этого текста».

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

Даже если это слишком сложно, какова основная концепция выполнения сортировки слиянием на месте?


person Lazer    schedule 03.04.2010    source источник
comment
Хороший вопрос, я задал его себе, читая вчера вопрос: stackoverflow.com/questions/2566459/   -  person Chris Lercher    schedule 03.04.2010
comment
Для справки, вот хорошая реализация стабильной сортировки слиянием на месте. Сложно, но не так уж и плохо. В итоге я реализовал как стабильная сортировка слиянием на месте и стабильная быстрая сортировка на месте в Java. Обратите внимание, что сложность O (n (log n) ^ 2)   -  person Thomas Mueller    schedule 09.12.2011
comment
Здесь описан довольно простой метод: xinok.wordpress.com/2014/08/17/   -  person Branko Dimitrijevic    schedule 31.01.2020


Ответы (8)


Кнут оставил это как упражнение (Том 3, 5.2.5). Существуют виды слияния на месте. Их нужно реализовывать осторожно.

Во-первых, простое слияние на месте, такое как описано здесь - неправильное решение. Производительность снижается до O (N 2).

Идея состоит в том, чтобы отсортировать часть массива, а остальную часть использовать как рабочую область для слияния.

Например, как следующая функция слияния.

void wmerge(Key* xs, int i, int m, int j, int n, int w) {
    while (i < m && j < n)
        swap(xs, w++, xs[i] < xs[j] ? i++ : j++);
    while (i < m)
        swap(xs, w++, i++);
    while (j < n)
        swap(xs, w++, j++);
}  

Он принимает массив xs, два отсортированных подмассива представлены как диапазоны [i, m) и [j, n) соответственно. Рабочая область начинается с w. Сравните со стандартным алгоритмом слияния, приведенным в большинстве учебников, он обменивается содержимым между отсортированным подмассивом и рабочей областью. В результате предыдущая рабочая область содержит объединенные отсортированные элементы, в то время как предыдущие элементы, хранящиеся в рабочей области, перемещаются в два подмассива.

Однако необходимо соблюдать два ограничения:

  1. Рабочая область должна находиться в пределах массива. Другими словами, он должен быть достаточно большим, чтобы удерживать элементы, которыми обмениваются, без возникновения каких-либо ошибок, выходящих за границы.
  2. Рабочая область может перекрываться любым из двух отсортированных массивов; однако он должен гарантировать, что ни один из несоединенных элементов не будет перезаписан.

Определив этот алгоритм слияния, легко представить решение, которое может отсортировать половину массива; Следующий вопрос: как поступить с остальной несортированной частью, хранящейся в рабочей области, как показано ниже:

... unsorted 1/2 array ... | ... sorted 1/2 array ...

Одна интуитивная идея состоит в том, чтобы рекурсивно отсортировать другую половину рабочей области, поэтому только 1/4 элемента еще не отсортированы.

... unsorted 1/4 array ... | sorted 1/4 array B | sorted 1/2 array A ...

Ключевым моментом на этом этапе является то, что мы должны рано или поздно объединить отсортированные 1/4 элементы B с отсортированными 1/2 элементами A.

Осталась ли рабочая область, которая содержит только 1/4 элемента, достаточно большой, чтобы объединить A и B? К сожалению, это не так.

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

Фактически, вместо того, чтобы сортировать вторую половину рабочей области, мы можем отсортировать первую половину и поместить рабочую область между двумя отсортированными массивами следующим образом:

... sorted 1/4 array B | unsorted work area | ... sorted 1/2 array A ...

Эта установка эффективно обеспечивает перекрытие рабочей области с подмассивом A. Эта идея предложена в [Jyrki Katajainen, Tomi Pasanen, Jukka Teuhola. `` Практическая сортировка слиянием на месте ''. Nordic Journal of Computing, 1996].

Итак, остается только повторить описанный выше шаг, который уменьшает рабочую область с 1/2, 1/4, 1/8,… Когда рабочая область становится достаточно маленькой (например, остается только два элемента), мы можем переключитесь на простую сортировку вставкой, чтобы завершить этот алгоритм.

Вот реализация на ANSI C, основанная на этой статье.

void imsort(Key* xs, int l, int u);

void swap(Key* xs, int i, int j) {
    Key tmp = xs[i]; xs[i] = xs[j]; xs[j] = tmp;
}

/* 
 * sort xs[l, u), and put result to working area w. 
 * constraint, len(w) == u - l
 */
void wsort(Key* xs, int l, int u, int w) {
    int m;
    if (u - l > 1) {
        m = l + (u - l) / 2;
        imsort(xs, l, m);
        imsort(xs, m, u);
        wmerge(xs, l, m, m, u, w);
    }
    else
        while (l < u)
            swap(xs, l++, w++);
}

void imsort(Key* xs, int l, int u) {
    int m, n, w;
    if (u - l > 1) {
        m = l + (u - l) / 2;
        w = l + u - m;
        wsort(xs, l, m, w); /* the last half contains sorted elements */
        while (w - l > 2) {
            n = w;
            w = l + (n - l + 1) / 2;
            wsort(xs, w, n, l);  /* the first half of the previous working area contains sorted elements */
            wmerge(xs, l, l + n - w, n, u, w);
        }
        for (n = w; n > l; --n) /*switch to insertion sort*/
            for (m = n; m < u && xs[m] < xs[m-1]; ++m)
                swap(xs, m, m - 1);
    }
}

Где wmerge определено ранее.

Полный исходный код можно найти здесь, а подробное объяснение можно найти здесь

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

person Larry LIU Xinyu    schedule 27.03.2013
comment
Я удалил ссылку на эту главу. Его содержание можно найти в главе 13 книги: sites.google. ru / site / algoxy / home / elementary-algorithmms.pdf. - person Larry LIU Xinyu; 11.11.2014
comment
Knuth left this as an exercise (Vol 3, 5.2.5). относится к ex. 13. [40] Реализуйте метод внутренней сортировки, предложенный [в конце этого раздела], производящий сортировку случайных данных в O (N) единицах времени только с точностью до O (sqrt (N)) дополнительные места в памяти.? (40 указывает на Довольно сложную или длительную задачу, которая, возможно, подходит в качестве курсового проекта в учебных ситуациях.) - person greybeard; 13.01.2016
comment
Я думаю, что временная сложность алгоритма на месте, упомянутого на сайте penguin.ew, равна O (log n * n ^ 2). Поскольку у нас есть log n слияний, и каждое слияние имеет порядок O (n ^ 2). Не правда ли? - person code4fun; 20.04.2016
comment
Этот алгоритм все еще стабилен и в n log n раз? - person Paul Stelian; 10.09.2017
comment
@PaulStelian - не стабильно. Элементы в рабочей области переупорядочиваются в соответствии с операциями упорядочивания элементов в отсортированной области. Это означает, что элементы рабочей области с равными значениями будут переупорядочены так, что они больше не будут в своем исходном порядке. - person rcgldr; 16.04.2018
comment
@rcgldr Ладно, это больше не представляет особого интереса. Мне известен алгоритм блочной сортировки слиянием, который, тем не менее, является стабильным. - person Paul Stelian; 16.04.2018
comment
@PaulStelian - Вики есть статья о сортировке слиянием блоков, которая, как вы отметили, является стабильной. Лучше всего работает, если имеется по крайней мере 2 · sqrt (n) уникальных значений, что позволяет переупорядочивать их, чтобы обеспечить рабочие области массива и оставаться стабильными. - person rcgldr; 27.04.2019
comment
Лучший случай алгоритма слияния на месте в mergeSort? Когда массив уже отсортирован, можем ли мы сказать O (m)? где m - размер 1-го отсортированного массива, который является одним из входов для алгоритма слияния. - person SpawN; 06.05.2021

В этом документе, включая «большой результат», описывается несколько вариантов сортировки слиянием на месте (PDF):

http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.22.5514&rep=rep1&type=pdf

Сортировка на месте с меньшим количеством перемещений

Юрки Катаянен, Томи А. Пасанен

Показано, что массив из n элементов может быть отсортирован с использованием O (1) дополнительного пространства, O (n log n / log log n) перемещений элемента и n log 2 n + O (n log log n) сравнения. Это первый алгоритм сортировки на месте, требующий o (n log n) перемещений в худшем случае, гарантируя при этом O (n log n) сравнений, но из-за задействованных постоянных факторов алгоритм представляет преимущественно теоретический интерес.

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

http://comjnl.oxfordjournals.org/cgi/content/abstract/38/8/681

Оптимальное стабильное слияние

Антониос Симвонис

В этой статье показано, как стабильно объединить две последовательности A и B размеров m и n, m ≤ n, соответственно, с присваиваниями O (m + n), сравнениями O (mlog (n / m + 1)) и с использованием только константы количество дополнительного места. Этот результат соответствует всем известным нижним границам ...

person Steve Jessop    schedule 03.04.2010

Это действительно непросто или эффективно, и я предлагаю вам не делать этого, если вам действительно не нужно (и вам, вероятно, не нужно, если это не домашнее задание, поскольку приложения слияния на месте в основном теоретические). Разве вы не можете вместо этого использовать быструю сортировку? Быстрая сортировка в любом случае будет быстрее благодаря нескольким более простым оптимизациям и дополнительной памяти O (log N).

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

Обратите внимание, что это медленнее даже классической сортировки слиянием, которой нет.

person IVlad    schedule 03.04.2010
comment
Быстрая сортировка нестабильна. Это действительно имеет значение для большого количества производственного кода. - person Donal Fellows; 03.04.2010
comment
быстрая сортировка может быть стабильной, а сортировка слиянием iirc не обязательно стабильна, если она на месте - person jk.; 03.04.2010
comment
Хорошие ссылки. Почему кто-то сказал, что это сложно? - person nsivakr; 09.08.2010
comment
@jk: Quicksort нестабилен; это скорость проистекает из этого, и вы не должны утверждать обратное. Это очень хороший компромисс. Да, можно связать исходный индекс с остальной частью ключа, чтобы у вас никогда не было двух одинаковых элементов, что дает стабильную сортировку; это требует затрат некоторого дополнительного пространства (линейного по количеству элементов), потому что вы не можете поддерживать относительный порядок «эквивалентных» элементов в противном случае, не прибегая к дополнительным движениям элементов, которые ухудшают производительность. - person Donal Fellows; 29.12.2010
comment
Quicksort также имеет наихудший случай O (n ^ 2) для специально созданного ввода - person HoboBen; 10.05.2011
comment
@DonalFellows: jk совершенно прав; quicksort МОЖЕТ быть реализована стабильно, без дополнительных затрат места. Проверьте Википедию. - person Rusty; 08.08.2012
comment
Слияние на месте практически полезно в C ++ (по крайней мере, до C ++ 11): некоторые объекты можно заменять, но нельзя копировать! - person user541686; 04.04.2013
comment
Эти парни разговаривают друг с другом. У быстрой сортировки есть много вариантов людей. Есть стабильная вариация и есть нестабильная вариация. Стабильная быстрая сортировка, конечно, стабильна, а нестабильная быстрая сортировка, конечно, нестабильна ... да. - person Pacerier; 06.11.2014
comment
Действительно ли дополнительная память Quicksort O (log n)? Я думал, что будучи алгоритмом на месте, это будет O (1) дополнительной памяти? О, будучи рекурсивным, использование стека - O (log n). - person kristianp; 16.01.2021
comment
Вне веб-архивов Сортировка слиянием на месте Джейсона Харрисона (two), для одного места, в этом коллекция реализаций сортировки, по-видимому, начатая самим человеком. Некоторые из них некорректно конвертируются в HTML, пока помечены таким образом (используйте исходный код). - person greybeard; 03.04.2021

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

Глядя на один шаг слияния:

[... список- отсортировано ... | x ... list- A ... | y ... список- B ...]

Мы знаем, что отсортированная последовательность меньше всего остального, что x меньше всего остального в A и что y < / em> меньше, чем все остальное в B. В случае, когда x меньше или равно y, вы просто перемещаете указатель в начало A на единице. В случае, когда y меньше, чем x, вам нужно перетасовать y мимо всего A для сортировки. Этот последний шаг и делает это дорого (кроме вырожденных случаев).

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

person Donal Fellows    schedule 03.04.2010
comment
Ваше слияние на месте имеет сложность наихудшего случая O (m n), где m - размер A, а n - размер B. Это тот случай, когда первый элемент в A больше, чем последний элемент в B. Сложность может быть улучшена до O (k log (k) + m + n), где k = min (m, n ) путем добавления кучи между A и B. Эта куча должна содержать элементы из A, которые больше остальных элементов в B, но меньше остальных элементов в A. Если A исчерпывается первым, то куча должна быть перемещена в конец B. В противном случае куча должна быть перемещена в начало A. Затем элементы кучи должны быть извлечены на месте и перевернуты, чтобы завершить слияние. - person valyala; 08.02.2012
comment
@valyala Обратите внимание, что при использовании кучи сортировка перестает быть стабильной. Кроме того, если вы используете кучу, вы можете использовать сортировку кучи вместо сортировки слиянием. - person martinkunev; 05.12.2014

Пример безбуферной сортировки слиянием в C.

#define SWAP(type, a, b) \
    do { type t=(a);(a)=(b);(b)=t; } while (0)

static void reverse_(int* a, int* b)
{
    for ( --b; a < b; a++, b-- )
       SWAP(int, *a, *b);
}
static int* rotate_(int* a, int* b, int* c)
/* swap the sequence [a,b) with [b,c). */
{
    if (a != b && b != c)
     {
       reverse_(a, b);
       reverse_(b, c);
       reverse_(a, c);
     }
    return a + (c - b);
}

static int* lower_bound_(int* a, int* b, const int key)
/* find first element not less than @p key in sorted sequence or end of
 * sequence (@p b) if not found. */
{
    int i;
    for ( i = b-a; i != 0; i /= 2 )
     {
       int* mid = a + i/2;
       if (*mid < key)
          a = mid + 1, i--;
     }
    return a;
}
static int* upper_bound_(int* a, int* b, const int key)
/* find first element greater than @p key in sorted sequence or end of
 * sequence (@p b) if not found. */
{
    int i;
    for ( i = b-a; i != 0; i /= 2 )
     {
       int* mid = a + i/2;
       if (*mid <= key)
          a = mid + 1, i--;
     }
    return a;
}

static void ip_merge_(int* a, int* b, int* c)
/* inplace merge. */
{
    int n1 = b - a;
    int n2 = c - b;

    if (n1 == 0 || n2 == 0)
       return;
    if (n1 == 1 && n2 == 1)
     {
       if (*b < *a)
          SWAP(int, *a, *b);
     }
    else
     {
       int* p, * q;

       if (n1 <= n2)
          p = upper_bound_(a, b, *(q = b+n2/2));
       else
          q = lower_bound_(b, c, *(p = a+n1/2));
       b = rotate_(p, b, q);

       ip_merge_(a, p, b);
       ip_merge_(b, q, c);
     }
}

void mergesort(int* v, int n)
{
    if (n > 1)
     {
       int h = n/2;
       mergesort(v, h); mergesort(v+h, n-h);
       ip_merge_(v, v+h, v+n);
     }
}

Пример адаптивной сортировки слиянием (оптимизирован).

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

#include <stdlib.h>
#include <string.h>

static int* copy_(const int* a, const int* b, int* out)
{
    int count = b - a;
    if (a != out)
       memcpy(out, a, count*sizeof(int));
    return out + count;
}
static int* copy_backward_(const int* a, const int* b, int* out)
{
    int count = b - a;
    if (b != out)
       memmove(out - count, a, count*sizeof(int));
    return out - count;
}

static int* merge_(const int* a1, const int* b1, const int* a2,
  const int* b2, int* out)
{
    while ( a1 != b1 && a2 != b2 )
       *out++ = (*a1 <= *a2) ? *a1++ : *a2++;
    return copy_(a2, b2, copy_(a1, b1, out));
}
static int* merge_backward_(const int* a1, const int* b1,
  const int* a2, const int* b2, int* out)
{
    while ( a1 != b1 && a2 != b2 )
       *--out = (*(b1-1) > *(b2-1)) ? *--b1 : *--b2;
    return copy_backward_(a1, b1, copy_backward_(a2, b2, out));
}

static unsigned int gcd_(unsigned int m, unsigned int n)
{
    while ( n != 0 )
     {
       unsigned int t = m % n;
       m = n;
       n = t;
     }
    return m;
}
static void rotate_inner_(const int length, const int stride,
  int* first, int* last)
{
    int* p, * next = first, x = *first;
    while ( 1 )
     {
       p = next;
       if ((next += stride) >= last)
          next -= length;
       if (next == first)
          break;
       *p = *next;
     }
    *p = x;
}
static int* rotate_(int* a, int* b, int* c)
/* swap the sequence [a,b) with [b,c). */
{
    if (a != b && b != c)
     {
       int n1 = c - a;
       int n2 = b - a;

       int* i = a;
       int* j = a + gcd_(n1, n2);

       for ( ; i != j; i++ )
          rotate_inner_(n1, n2, i, c);
     }
    return a + (c - b);
}

static void ip_merge_small_(int* a, int* b, int* c)
/* inplace merge.
 * @note faster for small sequences. */
{
    while ( a != b && b != c )
       if (*a <= *b)
          a++;
       else
        {
          int* p = b+1;
          while ( p != c && *p < *a )
             p++;
          rotate_(a, b, p);
          b = p;
        }
}
static void ip_merge_(int* a, int* b, int* c, int* t, const int ts)
/* inplace merge.
 * @note works with or without additional memory. */
{
    int n1 = b - a;
    int n2 = c - b;

    if (n1 <= n2 && n1 <= ts)
     {
       merge_(t, copy_(a, b, t), b, c, a);
     }
    else if (n2 <= ts)
     {
       merge_backward_(a, b, t, copy_(b, c, t), c);
     }
    /* merge without buffer. */
    else if (n1 + n2 < 48)
     {
       ip_merge_small_(a, b, c);
     }
    else
     {
       int* p, * q;

       if (n1 <= n2)
          p = upper_bound_(a, b, *(q = b+n2/2));
       else
          q = lower_bound_(b, c, *(p = a+n1/2));
       b = rotate_(p, b, q);

       ip_merge_(a, p, b, t, ts);
       ip_merge_(b, q, c, t, ts);
     }
}
static void ip_merge_chunk_(const int cs, int* a, int* b, int* t,
  const int ts)
{
    int* p = a + cs*2;
    for ( ; p <= b; a = p, p += cs*2 )
       ip_merge_(a, a+cs, p, t, ts);
    if (a+cs < b)
       ip_merge_(a, a+cs, b, t, ts);
}

static void smallsort_(int* a, int* b)
/* insertion sort.
 * @note any stable sort with low setup cost will do. */
{
    int* p, * q;
    for ( p = a+1; p < b; p++ )
     {
       int x = *p;
       for ( q = p; a < q && x < *(q-1); q-- )
          *q = *(q-1);
       *q = x;
     }
}
static void smallsort_chunk_(const int cs, int* a, int* b)
{
    int* p = a + cs;
    for ( ; p <= b; a = p, p += cs )
       smallsort_(a, p);
    smallsort_(a, b);
}

static void mergesort_lower_(int* v, int n, int* t, const int ts)
{
    int cs = 16;
    smallsort_chunk_(cs, v, v+n);
    for ( ; cs < n; cs *= 2 )
       ip_merge_chunk_(cs, v, v+n, t, ts);
}

static void* get_buffer_(int size, int* final)
{
    void* p = NULL;
    while ( size != 0 && (p = malloc(size)) == NULL )
       size /= 2;
    *final = size;
    return p;
}
void mergesort(int* v, int n)
{
    /* @note buffer size may be in the range [0,(n+1)/2]. */
    int request = (n+1)/2 * sizeof(int);
    int actual;
    int* t = (int*) get_buffer_(request, &actual);

    /* @note allocation failure okay. */
    int tsize = actual / sizeof(int);
    mergesort_lower_(v, n, t, tsize);
    free(t);
}
person Community    schedule 03.04.2014
comment
Вы это написали? Как он преодолевает трудности, выраженные в других ответах? Какое время его работы? - person Thomas Ahle; 25.04.2014
comment
Это адаптировано из моей собственной пользовательской библиотеки, но я не создавал эти алгоритмы, если это что вы спрашиваете. Рост O (n (log n) ^ 2) без вспомогательной памяти; O (n log n) с полным буфером. Это попытка быть практической реализацией и структурирована так, чтобы показать составляющие алгоритмы. - person Johnny Cage; 03.05.2014
comment
Зачем вам нужна рекурсия или дополнительный буфер для объединения двух отсортированных списков? Я думаю, что это можно сделать, переместив два указателя вперед и поменяв местами, если левый больше правого. - person jack; 29.12.2014

В этом ответе есть пример кода, который реализует алгоритм, описанный в статье Практическое слияние на месте Бинг-Чао Хуанг и Майкл А. Лэнгстон. Я должен признать, что не понимаю деталей, но данная сложность шага слияния составляет O (n).

С практической точки зрения есть свидетельства того, что чистые реализации на месте не работают лучше в реальных сценариях. Например, стандарт C ++ определяет std :: inplace_merge, который является именем подразумевает операцию слияния на месте.

Предполагая, что библиотеки C ++ обычно очень хорошо оптимизированы, интересно посмотреть, как это реализовано:

1) libstdc ++ (часть базы кода GCC): std :: inplace_merge

Реализация делегирует __ inplace_merge, который позволяет избежать проблемы, пытаясь выделить временный буфер:

typedef _Temporary_buffer<_BidirectionalIterator, _ValueType> _TmpBuf;
_TmpBuf __buf(__first, __len1 + __len2);

if (__buf.begin() == 0)
  std::__merge_without_buffer
    (__first, __middle, __last, __len1, __len2, __comp);
else
  std::__merge_adaptive
   (__first, __middle, __last, __len1, __len2, __buf.begin(),
     _DistanceType(__buf.size()), __comp);

В противном случае он возвращается к реализации (__ merge_without_buffer), который не требует дополнительной памяти, но больше не выполняется за время O (n).

2) libc ++ (часть базы кода Clang): std :: inplace_merge

Похоже. Он делегирует функцию функции, которая также пытается выделить буфер. В зависимости от того, достаточно ли элементов, он выберет реализацию. Резервная функция постоянной памяти называется __buffered_inplace_merge.

Возможно, даже резервный вариант - это время O (n), но дело в том, что они не используют реализацию, если доступна временная память.


Обратите внимание, что стандарт C ++ явно дает реализациям свободу выбора этого подхода, снижая требуемую сложность с O (n) до O (N log N):

Сложность: сравнение точно N-1, если доступно достаточно дополнительной памяти. Если памяти недостаточно, сравнивается O (N log N).

Конечно, это нельзя рассматривать как доказательство того, что постоянное слияние пространства на месте за O (n) времени никогда не должно использоваться. С другой стороны, если бы это было быстрее, оптимизированные библиотеки C ++, вероятно, переключились бы на этот тип реализации.

person Philipp Claßen    schedule 23.12.2018

Это моя версия C:

void mergesort(int *a, int len) {
  int temp, listsize, xsize;

  for (listsize = 1; listsize <= len; listsize*=2) {
    for (int i = 0, j = listsize; (j+listsize) <= len; i += (listsize*2), j += (listsize*2)) {
      merge(& a[i], listsize, listsize);
    }
  }

  listsize /= 2;

  xsize = len % listsize;
  if (xsize > 1)
    mergesort(& a[len-xsize], xsize);

  merge(a, listsize, xsize);
}

void merge(int *a, int sizei, int sizej) {
  int temp;
  int ii = 0;
  int ji = sizei;
  int flength = sizei+sizej;

  for (int f = 0; f < (flength-1); f++) {
    if (sizei == 0 || sizej == 0)
      break;

    if (a[ii] < a[ji]) {
      ii++;
      sizei--;
    }
    else {
      temp = a[ji];

      for (int z = (ji-1); z >= ii; z--)
        a[z+1] = a[z];  
      ii++;

      a[f] = temp;

      ji++;
      sizej--;
    }
  }
}
person Dylan Nissley    schedule 14.02.2012
comment
Обратите внимание, что эта реализация занимает Θ (n ^ 2 log n) времени в худшем случае (обратный массив). - person martinkunev; 06.02.2014

Я просто пробовал использовать алгоритм слияния для сортировки слиянием в JAVA, используя алгоритм сортировки вставкой, используя следующие шаги.
1) Доступны два отсортированных массива.
2) Сравните первые значения каждого массива; и поместите наименьшее значение в первый массив.
3) Поместите большее значение во второй массив, используя сортировку вставкой (переход слева направо).
4) Затем снова сравните второе значение первого массива и первое значение второго массива и сделаем то же самое. Но когда происходит подкачка, есть подсказка, что можно пропустить сравнение других элементов, а просто поменять местами нужно.

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

e.g)

Первый___ Массив: 3, 7, 8, 9

Второй массив: 1, 2, 4, 5

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

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

https://github.com/skanagavelu/algorithams/blob/master/src/sorting/MergeSort.java

package sorting;

import java.util.Arrays;

public class MergeSort {
    public static void main(String[] args) {
    int[] array = { 5, 6, 10, 3, 9, 2, 12, 1, 8, 7 };
    mergeSort(array, 0, array.length -1);
    System.out.println(Arrays.toString(array));

    int[] array1 = {4, 7, 2};
    System.out.println(Arrays.toString(array1));
    mergeSort(array1, 0, array1.length -1);
    System.out.println(Arrays.toString(array1));
    System.out.println("\n\n");

    int[] array2 = {4, 7, 9};
    System.out.println(Arrays.toString(array2));
    mergeSort(array2, 0, array2.length -1);
    System.out.println(Arrays.toString(array2));
    System.out.println("\n\n");

    int[] array3 = {4, 7, 5};
    System.out.println(Arrays.toString(array3));
    mergeSort(array3, 0, array3.length -1);
    System.out.println(Arrays.toString(array3));
    System.out.println("\n\n");

    int[] array4 = {7, 4, 2};
    System.out.println(Arrays.toString(array4));
    mergeSort(array4, 0, array4.length -1);
    System.out.println(Arrays.toString(array4));
    System.out.println("\n\n");

    int[] array5 = {7, 4, 9};
    System.out.println(Arrays.toString(array5));
    mergeSort(array5, 0, array5.length -1);
    System.out.println(Arrays.toString(array5));
    System.out.println("\n\n");

    int[] array6 = {7, 4, 5};
    System.out.println(Arrays.toString(array6));
    mergeSort(array6, 0, array6.length -1);
    System.out.println(Arrays.toString(array6));
    System.out.println("\n\n");

    //Handling array of size two
    int[] array7 = {7, 4};
    System.out.println(Arrays.toString(array7));
    mergeSort(array7, 0, array7.length -1);
    System.out.println(Arrays.toString(array7));
    System.out.println("\n\n");

    int input1[] = {1};
    int input2[] = {4,2};
    int input3[] = {6,2,9};
    int input4[] = {6,-1,10,4,11,14,19,12,18};
    System.out.println(Arrays.toString(input1));
    mergeSort(input1, 0, input1.length-1);
    System.out.println(Arrays.toString(input1));
    System.out.println("\n\n");

    System.out.println(Arrays.toString(input2));
    mergeSort(input2, 0, input2.length-1);
    System.out.println(Arrays.toString(input2));
    System.out.println("\n\n");

    System.out.println(Arrays.toString(input3));
    mergeSort(input3, 0, input3.length-1);
    System.out.println(Arrays.toString(input3));
    System.out.println("\n\n");

    System.out.println(Arrays.toString(input4));
    mergeSort(input4, 0, input4.length-1);
    System.out.println(Arrays.toString(input4));
    System.out.println("\n\n");
}

private static void mergeSort(int[] array, int p, int r) {
    //Both below mid finding is fine.
    int mid = (r - p)/2 + p;
    int mid1 = (r + p)/2;
    if(mid != mid1) {
        System.out.println(" Mid is mismatching:" + mid + "/" + mid1+ "  for p:"+p+"  r:"+r);
    }

    if(p < r) {
        mergeSort(array, p, mid);
        mergeSort(array, mid+1, r);
//      merge(array, p, mid, r);
        inPlaceMerge(array, p, mid, r);
        }
    }

//Regular merge
private static void merge(int[] array, int p, int mid, int r) {
    int lengthOfLeftArray = mid - p + 1; // This is important to add +1.
    int lengthOfRightArray = r - mid;

    int[] left = new int[lengthOfLeftArray];
    int[] right = new int[lengthOfRightArray];

    for(int i = p, j = 0; i <= mid; ){
        left[j++] = array[i++];
    }

    for(int i = mid + 1, j = 0; i <= r; ){
        right[j++] = array[i++];
    }

    int i = 0, j = 0;
    for(; i < left.length && j < right.length; ) {
        if(left[i] < right[j]){
            array[p++] = left[i++];
        } else {
            array[p++] = right[j++];
        }
    }
    while(j < right.length){
        array[p++] = right[j++];
    } 
    while(i < left.length){
        array[p++] = left[i++];
    }
}

//InPlaceMerge no extra array
private static void inPlaceMerge(int[] array, int p, int mid, int r) {
    int secondArrayStart = mid+1;
    int prevPlaced = mid+1;
    int q = mid+1;
    while(p < mid+1 && q <= r){
        boolean swapped = false;
        if(array[p] > array[q]) {
            swap(array, p, q);
            swapped = true;
        }   
        if(q != secondArrayStart && array[p] > array[secondArrayStart]) {
            swap(array, p, secondArrayStart);
            swapped = true;
        }
        //Check swapped value is in right place of second sorted array
        if(swapped && secondArrayStart+1 <= r && array[secondArrayStart+1] < array[secondArrayStart]) {
            prevPlaced = placeInOrder(array, secondArrayStart, prevPlaced);
        }
        p++;
        if(q < r) {     //q+1 <= r) {
            q++;
        }
    }
}

private static int placeInOrder(int[] array, int secondArrayStart, int prevPlaced) {
    int i = secondArrayStart;
    for(; i < array.length; i++) {
        //Simply swap till the prevPlaced position
        if(secondArrayStart < prevPlaced) {
            swap(array, secondArrayStart, secondArrayStart+1);
            secondArrayStart++;
            continue;
        }
        if(array[i] < array[secondArrayStart]) {
            swap(array, i, secondArrayStart);
            secondArrayStart++;
        } else if(i != secondArrayStart && array[i] > array[secondArrayStart]){
            break;
        }
    }
    return secondArrayStart;
}

private static void swap(int[] array, int m, int n){
    int temp = array[m];
    array[m] = array[n];
    array[n] = temp;
}
}
person Kanagavelu Sugumar    schedule 25.02.2016
comment
Это как O (n ^ 2), так и очень нечитаемое (из-за случайных синтаксических ошибок и непоследовательного / плохого стиля) - person glaba; 31.08.2018