Як сортувати на місці за допомогою алгоритму сортування злиття?


244

Я знаю, питання не надто конкретне.

Все, що я хочу, - це хтось мені підказати, як перетворити звичайний сортування злиття в місце злиття (або сортування з постійним додатковим простором).

Все, що я можу знайти (в мережі), - це сторінки, на яких написано "це занадто складно" або "поза рамками цього тексту".

Єдині відомі способи злиття на місці (без зайвого місця) занадто складні, щоб звести їх до практичної програми. (взято звідси )

Навіть якщо вона занадто складна, яка основна концепція того, як зробити сортування злиття на місці?


Приємне запитання, я задав це собі, читаючи запитання від вчорашнього дня: stackoverflow.com/questions/2566459/…
Кріс Лерчер

Тут описаний досить простий метод: xinok.wordpress.com/2014/08/17/…
Бранко Димитрієвич

Відповіді:


140

Кнут залишив це як вправу (т. 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.

Повний вихідний код можна знайти тут, а детальне пояснення можна знайти тут

До речі, ця версія не найшвидший сорт злиття, тому що їй потрібно більше операцій своп. Згідно з моїм тестом, це швидше, ніж у стандартній версії, яка виділяє додаткові місця у кожній рекурсії. Але це повільніше, ніж оптимізована версія, яка заздалегідь подвоює початковий масив і використовує його для подальшого злиття.


6
Knuth left this as an exercise (Vol 3, 5.2.5).відноситься до екс. 13. [40] Реалізуйте запропонований метод внутрішнього сортування [в кінці цього розділу], створюючи який сортує випадкові дані в O (N) одиницях часу, що мають лише O (sqrt (N)) додаткові місця пам'яті. ? ( 40, що вказує на досить складну чи тривалу проблему, яка, можливо, підходить як проект в навчальних ситуаціях. )
сіра борода

4
Я думаю, що часова складність алгоритму на місці, згаданого на сайті penguin.ew, є O (log n * n ^ 2). Оскільки у нас є n n злиття, і кожне злиття має порядок O (n ^ 2). Хіба це не так?
code4fun

1
Чи все ще цей алгоритм стабільний і за n n n n часу?
Пол Стеліан

1
@PaulStelian - це не стабільно. Елементи в робочій зоні переставляються відповідно до операцій упорядкування елементів у відсортованій області. Це означає, що елементи робочої області з рівними значеннями будуть переставлятись, щоб вони більше не були у своєму початковому порядку.
rcgldr

1
@PaulStelian - У Вікі є стаття про сортування блоку злиття , яка, як ви коментували, є стабільною. Найкраще він працює, якщо є принаймні 2 · sqrt (n) унікальних значень, що дозволяє їх переупорядкувати, щоб забезпечити робочі області масиву і залишатися стабільними.
rcgldr

59

Цей документ, включаючи його "великий результат", описує кілька варіантів місцевого злиття (PDF):

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

Сортування на місці з меншою кількістю рухів

Jyrki Katajainen, Tomi A. Pasanen

Показано, що масив з 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/ab Abstract/38/8/681

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

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

У цій роботі показано, як стабільно об'єднувати дві послідовності A і B розмірів m і n, m ≤ n відповідно з призначеннями O (m + n), O (mlog (n / m + 1)) зіставленнями і використовуючи лише постійну кількість додаткового місця. Цей результат відповідає всім відомим нижнім межам ...


12

Це насправді непросто і не ефективно, і я пропоную вам не робити цього, якщо вам це не доведеться (і, мабуть, не доведеться, якщо це не домашнє завдання, оскільки програми заміщення об'єднань здебільшого теоретичні). Не можете замість цього використати кікспорт? У будь-якому випадку Quicksort стане швидшим за допомогою декількох простіших оптимізацій, а додаткова пам'ять - O (log N) .

У будь-якому випадку, якщо ти повинен це зробити, то повинен. Ось що я знайшов: один і два . Я не знайомий зі своїм об'єднанням inplace, але здається, що основна ідея - використовувати обертання для полегшення об’єднання двох масивів без використання додаткової пам'яті.

Зауважте, що це повільніше, ніж класичний сорт злиття, який не має місця.


9
Квіксор не стабільний. Це дуже важливо для багатьох виробничих кодів.
Стипендіати доналу

7
quicksort може бути стабільним, і сортування iirc злиття не обов'язково є стабільним, якщо на місці
jk.

4
@jk: Quicksort не стабільний; швидкість випливає з цього, і ви не повинні намагатися заявляти інакше. Це дуже хороший компроміс. Так, можна пов’язати оригінальний індекс з рештою ключа, щоб у вас ніколи не було двох елементів однакових, що дає стабільний сорт; що виходить за необхідної вартості додаткового простору (лінійного за кількістю елементів), оскільки ви не можете підтримувати відносний порядок "еквівалентних" елементів в іншому випадку, не вдаючись до додаткових рухів елементів, які руйнують продуктивність.
стипендіати

4
У Quicksort також є O (n ^ 2) найгірший випадок для спеціально створеного введення
HoboBen

4
@DonalFellows: jk - це абсолютно правильно; КВІТКОР МОЖЕ бути реалізований стабільним, без додаткових витрат на місце. Перевірте Вікіпедію.
Іржавий

10

Найважливішим кроком є ​​отримання самого злиття на місці. Це не так складно, як ці джерела, але ви щось втрачаєте, намагаючись.

Дивлячись на один крок злиття:

[... список- сортований ... | х ... список- А ... | y ... список- B ...]

Ми знаємо , що відсортована послідовність менше , ніж все інше, що х менше , ніж все інше в A , і що у менше , ніж все інше в B . У випадку, коли х менше або дорівнює y , ви просто перемістите вказівник на початок A на одному. У тому випадку , коли у менше х , ви повинні перетасувати у мимо всіх А до відсортований . Цей останній крок - це те, що робить це дорого (за винятком випадків виродження).

Це загалом дешевше (особливо, коли масиви фактично містять поодинокі слова на елемент, наприклад, вказівник на рядок або структуру), щоб торгувати деяким простором на час і мати окремий тимчасовий масив, який ви сортуєте назад і назад.


5
Ваше злиття на місці має найгірший складний рівень O (m n), де m - розмір A, а n - розмір B. Це той випадок, коли перший елемент у A більший, ніж останній елемент у B. Складність може бути поліпшена до O (k log (k) + m + n), де k = min (m, n) шляхом додавання a купа між A і B. Ця купа повинна містити предмети з A, які є більшими, ніж інші елементи в B, але менші, ніж решта елементів у A. Якщо A спочатку вичерпано, то купу слід перемістити в кінець B. В іншому випадку купу потрібно перемістити на початок А. Тоді купу елементів потрібно вискочити на місці і повернути назад, щоб завершити злиття.
valyala

2
@valyala Зауважте, що при використанні купи сорт вже не стійкий. Крім того, якщо ви використовуєте купу, ви можете перейти з сортуванням купи замість об'єднання сортування.
мартінкунев

8

Тільки для довідки, ось приємна реалізація стабільного на місцях злиття . Складний, але не надто поганий.

Я в кінцевому підсумку застосував як стабільний на місці злиття сорт, так і стабільний на місці місцях швидкий вибір . Зверніть увагу, складність становить O (n (log n) ^ 2)


4

Приклад злиття без буфера в С.

#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);
}

2
Ви це написали? Як це долає труднощі, виражені в інших відповідях? Який його час роботи?
Thomas Ahle

Це адаптовано з моєї власної власної бібліотеки , але я не створив ці алгоритми, якщо це те, що ви запитуєте. Зростання - це O (n (log n) ^ 2) без допоміжної пам’яті; O (n log n) з повним буфером. Це намагається бути практичною реалізацією та побудовано для відображення складових алгоритмів.
Джонні Кейдж

Для чого потрібна рекурсія або додатковий буфер, щоб об'єднати два відсортовані списки? Я думаю, що це можна зробити, перемістивши два покажчики вперед і поміняючи місцями, якщо лівий більший за правий.
джек

3

У цій відповіді є приклад коду , який реалізує алгоритм, описаний у статті Практичне об'єднання на місцях Бін-Чао Хуан та Майкла А. Ленґстона. Я мушу визнати, що я не розумію деталей, але задана складність кроку злиття - 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 ++, ймовірно, перейдуть на такий тип реалізації.


2

Це моя версія 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--;
    }
  }
}

Зауважте, що ця реалізація займає Θ (n ^ 2 log n) часу в гіршому випадку (зворотний масив).
мартінкунев

1

Існує відносно проста реалізація місцевого сортування злиття за допомогою оригінальної техніки Кронрода, але з більш простою реалізацією. Мальовничий приклад, який ілюструє цю техніку, можна знайти тут: http://www.logiccoder.com/TheSortProblem/BestMergeInfo.htm .

Також є посилання на більш детальний теоретичний аналіз того ж автора, пов’язаний із цим посиланням.


це посилання призводить до 403
Шарлотта Тан

3
Посилання виправлено. Документація там є виразною до точки тупості. У мене складається враження, що там цікава ідея, але алгоритм не представлений, лише набір діаграм і кілька досить слабких описів. Мені не вдалося цікавим чином прив’язати слабкі описи до діаграм, тому я відмовився.
Іра Бакстер

-6

Я просто спробував встановити алгоритм злиття місця для сортування злиття в JAVA , використовуючи алгоритм сортування вставки, виконуючи наступні кроки.
1) Наявні два сортовані масиви.
2) Порівняйте перші значення кожного масиву; і помістіть найменше значення в перший масив.
3) Помістіть велике значення у другий масив, використовуючи сортування вставки (прокрутка зліва направо).
4) Потім знову порівняйте друге значення першого масиву і перше значення другого масиву, і зробіть те саме. Але коли відбувається заміна, є деяка підказка пропустити порівняння подальших пунктів, а потрібна просто заміна.

Я зробив тут деяку оптимізацію; щоб зменшити порівняння у вставці.
Єдиний недолік, який я знайшов у цьому рішенні, це те, що він потребує більшої заміни елементів масиву у другому масиві.

наприклад)

First___Array: 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;
}
}

3
Це одночасно O (n ^ 2), а також дуже нечитабельне (через випадкові синтаксичні помилки та непослідовний / поганий стиль)
glaba
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.