Come ordinare sul posto utilizzando l'algoritmo Merge Sort?


244

So che la domanda non è troppo specifica.

Tutto quello che voglio è qualcuno che mi dica come convertire un normale ordinamento di unione in un ordinamento di unione sul posto (o un ordinamento di unione con sovraccarico costante di spazio extra).

Tutto quello che riesco a trovare (in rete) sono le pagine che dicono "è troppo complesso" o "fuori dalla portata di questo testo".

Gli unici modi noti per unire sul posto (senza spazio aggiuntivo) sono troppo complessi per essere ridotti al programma pratico. (preso da qui )

Anche se è troppo complesso, qual è il concetto di base su come rendere il merge sort sul posto?


Bella domanda, ho chiesto io stesso durante la lettura attraverso una domanda di ieri: stackoverflow.com/questions/2566459/...
Chris Lercher

C'è un metodo abbastanza semplice qui descritto: xinok.wordpress.com/2014/08/17/…
Branko Dimitrijevic

Risposte:


140

Knuth lo ha lasciato come un esercizio (Vol 3, 5.2.5). Esistono tipi di unione sul posto. Devono essere implementati con cura.

Innanzitutto, l'ingenua unione sul posto come quella qui descritta non è la soluzione giusta. Riduce le prestazioni a O (N 2 ) .

L'idea è quella di ordinare parte dell'array mentre si utilizza il resto come area di lavoro per l'unione.

Ad esempio, come la seguente funzione di unione.

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

Prende l'array xs, i due sotto-array ordinati sono rappresentati come intervalli [i, m)e [j, n)rispettivamente. L'area di lavoro inizia da w. Confronta con l'algoritmo di unione standard fornito nella maggior parte dei libri di testo, questo scambia i contenuti tra l'array secondario ordinato e l'area di lavoro. Di conseguenza, l'area di lavoro precedente contiene gli elementi ordinati uniti, mentre gli elementi precedenti memorizzati nell'area di lavoro vengono spostati nei due sotto-array.

Tuttavia, ci sono due vincoli che devono essere soddisfatti:

  1. L'area di lavoro dovrebbe essere all'interno dei limiti dell'array. In altre parole, dovrebbe essere abbastanza grande da contenere gli elementi scambiati senza causare errori fuori limite.
  2. L'area di lavoro può essere sovrapposta con uno dei due array ordinati; tuttavia, deve garantire che nessuno degli elementi non uniti venga sovrascritto.

Con questo algoritmo di fusione definito, è facile immaginare una soluzione, che può ordinare metà dell'array; La domanda successiva è: come gestire il resto della parte non ordinata memorizzata nell'area di lavoro, come mostrato di seguito:

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

Un'idea intuitiva è quella di ordinare in modo ricorsivo un'altra metà dell'area di lavoro, quindi ci sono solo 1/4 elementi non ancora ordinati.

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

Il punto chiave in questa fase è che prima o poi dobbiamo unire gli elementi 1/4 ordinati B con gli elementi 1/2 ordinati A.

L'area di lavoro è rimasta, che contiene solo 1/4 elementi, abbastanza grande da unire A e B? Sfortunatamente no.

Tuttavia, il secondo vincolo di cui sopra ci dà un suggerimento, che possiamo sfruttarlo disponendo l'area di lavoro in modo che si sovrapponga con uno dei due sotto-array se possiamo garantire la sequenza di fusione in modo che gli elementi non uniti non vengano sovrascritti.

In realtà, invece di ordinare la seconda metà dell'area di lavoro, possiamo ordinare la prima metà e posizionare l'area di lavoro tra i due array ordinati in questo modo:

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

Questa configurazione organizza efficacemente la sovrapposizione dell'area di lavoro con il sotto-array A. Questa idea è proposta in [Jyrki Katajainen, Tomi Pasanen, Jukka Teuhola. `` Fusione sul posto pratica ''. Nordic Journal of Computing, 1996].

Quindi l'unica cosa rimasta è ripetere il passaggio precedente, che riduce l'area di lavoro da 1/2, 1/4, 1/8, ... Quando l'area di lavoro diventa abbastanza piccola (ad esempio, rimangono solo due elementi), possiamo passare a un ordinamento di inserimento banale per terminare questo algoritmo.

Ecco l'implementazione in ANSI C basata su questo documento.

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

Dove wmerge è stato definito in precedenza.

Il codice sorgente completo può essere trovato qui e la spiegazione dettagliata può essere trovata qui

A proposito, questa versione non è l'ordinamento di unione più veloce perché ha bisogno di più operazioni di scambio. Secondo il mio test, è più veloce della versione standard, che alloca spazi extra in ogni ricorsione. Ma è più lento della versione ottimizzata, che raddoppia in anticipo l'array originale e lo utilizza per un'ulteriore fusione.


6
Knuth left this as an exercise (Vol 3, 5.2.5).si riferisce a ex. 13. [40] Implementare il metodo di ordinamento interno suggerito [alla fine di questa sezione], producendo che ordina i dati casuali in O (N) unità di tempo con solo O (sqrt (N)) posizioni di memoria aggiuntive. ? ( 40 indica un problema piuttosto lungo o difficile, forse adatto come progetto a termine in situazioni di classe. )
barbarossa

4
Penso che la complessità temporale dell'algoritmo sul posto menzionata nel sito penguin.ew sia O (log n * n ^ 2). Da quando abbiamo log n si fonde e ogni unione è dell'ordine O (n ^ 2). Non è vero?
code4fun,

1
Questo algoritmo è ancora stabile e in n log n time?
Paul Stelian,

1
@PaulStelian - non è stabile. Gli elementi nell'area di lavoro vengono riorganizzati in base alle operazioni di ordinamento sugli elementi nell'area selezionata. Ciò significa che gli elementi dell'area di lavoro con valori uguali verranno riorganizzati in modo che non siano più nell'ordine originale.
rcgldr,

1
@PaulStelian - Wiki ha un articolo per l' ordinamento del blocco unione , che come hai commentato è stabile. Funziona meglio se ci sono almeno 2 · sqrt (n) valori univoci, che consente di riordinarli per fornire aree di lavoro di un array e rimanere stabili.
rcgldr,

59

Compreso il suo "grande risultato", questo documento descrive un paio di varianti del tipo di unione sul posto (PDF):

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

Ordinamento sul posto con meno mosse

Jyrki Katajainen, Tomi A. Pasanen

Viene mostrato che un array di n elementi può essere ordinato usando O (1) spazio extra, O (n log n / log log n) elemento si sposta e n log 2 n + O (n log log n) confronti. Questo è il primo algoritmo di ordinamento sul posto che richiede spostamenti di o (n log n) nel peggiore dei casi garantendo allo stesso tempo confronti di O (n log n), ma a causa dei fattori costanti coinvolti l'algoritmo è prevalentemente di interesse teorico.

Penso che anche questo sia rilevante. Ne ho una stampa in giro, mi è stata trasmessa da un collega, ma non l'ho letto. Sembra coprire la teoria di base, ma non ho abbastanza familiarità con l'argomento per giudicare in modo esauriente:

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

Fusione ottimale e stabile

Antonios Symvonis

Questo documento mostra come unire stabilmente due sequenze A e B di dimensioni m e n, m ≤ n, rispettivamente, con assegnazioni O (m + n), confronti O (mlog (n / m + 1)) e usando solo una costante quantità di spazio aggiuntivo. Questo risultato corrisponde a tutti i limiti inferiori noti ...


12

In realtà non è facile o efficiente, e suggerisco di non farlo a meno che non sia necessario (e probabilmente non è necessario a meno che non si tratti di compiti a casa poiché le applicazioni della fusione sul posto sono per lo più teoriche). Non puoi usare quicksort invece? Quicksort sarà comunque più veloce con alcune ottimizzazioni più semplici e la sua memoria aggiuntiva è O (log N) .

Ad ogni modo, se devi farlo, allora devi farlo. Ecco cosa ho trovato: uno e due . Non ho familiarità con il tipo di unione inplace, ma sembra che l'idea di base sia utilizzare le rotazioni per facilitare l'unione di due array senza utilizzare memoria aggiuntiva.

Si noti che questo è più lento anche del classico ordinamento di unione che non è in atto.


9
Quicksort non è stabile. Ciò conta davvero per un sacco di codice di produzione.
Donal Fellows,

7
quicksort può essere stabile e iirc merge sort non è necessariamente stabile se in posizione
jk.

4
@jk: Quicksort non è stabile; la sua velocità deriva da questo e non dovresti provare a pretendere diversamente. È un ottimo compromesso. Sì, è possibile associare l'indice originale al resto della chiave in modo da non avere mai due elementi uguali, dando un ordinamento stabile; ciò comporta un costo necessario per lo spazio aggiuntivo (lineare nel numero di elementi) perché non è possibile mantenere l'ordine relativo di elementi "equivalenti" altrimenti senza ricorrere a movimenti di elementi aggiuntivi che distruggono le prestazioni.
Donal Fellows,

4
Quicksort ha anche un caso peggiore O (n ^ 2) per input appositamente predisposto
HoboBen,

4
@DonalFellows: jk ha esattamente ragione; quicksort PU be essere implementato per essere stabile, senza costi di spazio aggiuntivi. Controlla Wikipedia.
Rusty

10

Il passaggio fondamentale è ottenere l' unione stessa sul posto. Non è difficile come quelle fonti distinguono, ma perdi qualcosa quando provi.

Guardando un passaggio dell'unione:

[... elenco- ordinato ... | x ... elenco- A ... | y ... list- B ...]

Sappiamo che l' ordinata sequenza è meno di tutto il resto, che x è inferiore a tutto il resto in A , e che y è inferiore a tutto il resto in B . Nel caso in cui x sia minore o uguale a y , basta spostare il puntatore all'inizio di A su uno. Nel caso in cui y sia inferiore a x , devi mescolare y oltre l'intero A per ordinare . L'ultimo passo è ciò che rende questo costoso (tranne nei casi degeneri).

In genere è più economico (specialmente quando le matrici contengono effettivamente singole parole per elemento, ad esempio un puntatore a una stringa o una struttura) per scambiare spazio per il tempo e avere un array temporaneo separato che si ordina avanti e indietro.


5
La tua fusione sul posto ha O (m n) complessità nel caso peggiore, dove m è una dimensione A e n è una dimensione B. Questo è il caso in cui il primo elemento in A è più grande dell'ultimo in B. La complessità può essere migliorata in O (k log (k) + m + n), dove k = min (m, n) aggiungendo un heap tra A e B. Questo heap dovrebbe contenere elementi provenienti da A, che sono più grandi degli elementi rimanenti in B, ma più piccoli degli elementi rimanenti in A. Se A viene esaurito per primo, l'heap deve essere spostato alla fine di B. In caso contrario, l'heap deve essere spostato all'inizio di A. Quindi gli elementi heap devono essere espulsi sul posto e invertiti per completare l'unione.
valyala,

2
@valyala Nota che quando si utilizza un heap, l'ordinamento non è più stabile. Inoltre, se si utilizza un heap, è possibile utilizzare l'heap ordinamento anziché unire l'ordinamento.
martinkunev,


4

Un esempio di fusione senza buffer in 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);
     }
}

Un esempio di fusione adattiva (ottimizzata).

Aggiunge codice di supporto e modifiche per accelerare l'unione quando è disponibile un buffer ausiliario di qualsiasi dimensione (funziona ancora senza memoria aggiuntiva). Utilizza l'unione avanti e indietro, la rotazione dell'anello, l'unione e l'ordinamento di piccole sequenze e l'unione iterativa.

#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
Hai scritto questo? In che modo supera le difficoltà espresse nelle altre risposte? Qual è il suo tempo di esecuzione?
Thomas Ahle,

Questo è adattato dalla mia libreria personalizzata , ma non ho creato questi algoritmi se è quello che stai chiedendo. La crescita è O (n (log n) ^ 2) senza memoria ausiliaria; O (n log n) con buffer completo. Questo cerca di essere un'implementazione pratica ed è strutturato per mostrare algoritmi costituenti.
Johnny Cage

Perché è necessario ricorsione o buffer aggiuntivo per unire due elenchi ordinati? Penso che possa essere fatto spostando i due puntatori in avanti e scambiando se sinistra è più grande di destra.
Jack

3

Questa risposta ha un esempio di codice , che implementa l'algoritmo descritto nel documento Pratica fusione sul posto di Bing-Chao Huang e Michael A. Langston. Devo ammettere che non capisco i dettagli, ma la complessità data della fase di unione è O (n).

Da un punto di vista pratico, ci sono prove che le implementazioni sul posto pure non stanno andando meglio negli scenari del mondo reale. Ad esempio, lo standard C ++ definisce std :: inplace_merge , che è come il nome implica un'operazione di unione sul posto.

Supponendo che le librerie C ++ siano in genere ottimizzate molto bene, è interessante vedere come viene implementato:

1) libstdc ++ (parte della base di codice GCC): std :: inplace_merge

L'implementazione delega a __inplace_merge , che schiva il problema tentando di allocare un buffer temporaneo:

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

Altrimenti, ricade in un'implementazione ( __merge_without_buffer ), che non richiede memoria aggiuntiva, ma non viene più eseguita in O (n) time.

2) libc ++ (parte della base di codice Clang): std :: inplace_merge

Sembra simile Delega a una funzione , che tenta anche di allocare un buffer . A seconda che abbia abbastanza elementi, sceglierà l'implementazione. La funzione di fallback a memoria costante si chiama __buffered_inplace_merge .

Forse anche il fallback è ancora O (n) tempo, ma il punto è che non usano l'implementazione se è disponibile memoria temporanea.


Si noti che lo standard C ++ offre esplicitamente alle implementazioni la libertà di scegliere questo approccio riducendo la complessità richiesta da O (n) a O (N log N):

Complessità: esattamente N-1 se è disponibile memoria aggiuntiva sufficiente. Se la memoria è insufficiente, il confronto O (N log N).

Naturalmente, questo non può essere preso come prova che lo spazio costante sul posto si fonde nel tempo O (n) non dovrebbe mai essere usato. D'altra parte, se fosse più veloce, le librerie C ++ ottimizzate passerebbero probabilmente a quel tipo di implementazione.


2

Questa è la mia versione 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--;
    }
  }
}

Si noti che questa implementazione richiede ^ (n ^ 2 log n) tempo nel peggiore dei casi (matrice inversa).
martinkunev,

1

Esiste un'implementazione relativamente semplice dell'ordinamento sul posto utilizzando la tecnica originale di Kronrod ma con un'implementazione più semplice. Un esempio pittorico che illustra questa tecnica può essere trovato qui: http://www.logiccoder.com/TheSortProblem/BestMergeInfo.htm .

Ci sono anche collegamenti ad analisi teoriche più dettagliate dello stesso autore associate a questo collegamento.


questo link si traduce in un 403
Charlotte Tan

3
Il collegamento è stato risolto. La documentazione è criptica fino al punto di ottusità. Ho l'impressione che ci sia un'idea interessante lì, ma non viene presentato alcun algoritmo, solo una serie di diagrammi e alcune descrizioni piuttosto deboli. Non sono stato in grado di legare le descrizioni deboli ai diagrammi in modo interessante, quindi ho rinunciato.
Ira Baxter,

-6

Ho appena provato a creare un algoritmo di merge per merge sort in JAVA usando l'algoritmo di ordinamento di inserzione, seguendo i seguenti passaggi.
1) Sono disponibili due array ordinati.
2) Confronta i primi valori di ciascun array; e posizionare il valore più piccolo nel primo array.
3) Posizionare il valore più grande nel secondo array utilizzando l'ordinamento per inserzione (attraversare da sinistra a destra).
4) Quindi confronta nuovamente il secondo valore del primo array e il primo valore del secondo array e fai lo stesso. Ma quando si verifica lo scambio c'è qualche indizio su come saltare confrontando gli altri elementi, ma è necessario solo lo scambio.

Ho fatto qualche ottimizzazione qui; per mantenere un confronto minore nell'ordinamento per inserzione.
L'unico inconveniente che ho riscontrato con queste soluzioni è che ha bisogno di un maggiore scambio di elementi dell'array nel secondo array.

per esempio)

First___Array: 3, 7, 8, 9

Secondo array: 1, 2, 4, 5

Quindi 7, 8, 9 fa scambiare il secondo array (sposta di sinistra di uno) tutti i suoi elementi di uno ogni volta per posizionarsi nell'ultimo.

Quindi il presupposto qui è scambiare elementi è trascurabile rispetto al confronto di due elementi.

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
È sia O (n ^ 2) che anche altamente illeggibile (a causa di errori di sintassi occasionali e stile incoerente / scadente)
glaba
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.