Quicksort: Scegliere il perno

voti
94

Nell'attuare Quicksort, una delle cose che dovete fare è scegliere un perno. Ma quando guardo pseudocodice come quella qui sotto, non è chiaro come dovrei scegliere il perno. Primo elemento di lista? Qualcos'altro?

 function quicksort(array)
     var list less, greater
     if length(array) ≤ 1  
         return array  
     select and remove a pivot value pivot from array
     for each x in array
         if x ≤ pivot then append x to less
         else append x to greater
     return concatenate(quicksort(less), pivot, quicksort(greater))

Qualcuno può aiutarmi a afferrare il concetto di scelta di un pivot e dell'esistenza o meno di diversi scenari chiamo per strategie diverse.

È pubblicato 02/10/2008 alle 20:37
fonte dall'utente
In altre lingue...                            


13 risposte

voti
72

La scelta di un perno casuale riduce al minimo la possibilità che si incontrano nel caso peggiore O (n 2 ) le prestazioni (sempre scegliere prima o l'ultima causerebbe prestazioni nel caso peggiore per i dati di quasi-ordinati o quasi-inversione-ordinati). Scegliendo l'elemento centrale sarebbe accettabile nella maggior parte dei casi.

Inoltre, se si implementa da soli, ci sono versioni dell'algoritmo che lavorano sul posto (cioè senza la creazione di due nuove liste e poi concatenare).

Risposto il 02/10/2008 a 20:41
fonte dall'utente

voti
47

Dipende dalle vostre esigenze. La scelta di un perno a caso rende più difficile per creare un insieme di dati che genera O prestazioni (N ^ 2). 'Mediana-di-tre' (primo, ultimo, al centro) è anche un modo per evitare problemi. Attenzione alle performance relativa di confronti, comunque; se i confronti sono costosi, quindi Mo3 fa più confronti di scelta (un singolo valore pivot) a caso. record del database possono essere costosi per confrontare.


Aggiornamento: Tirando commenti in risposta.

mdkess affermato:

'Mediana di 3' non è la prima lo scorso mezzo. Scegli tre indici casuali, e prendere il valore centrale di questo. Il punto è quello di fare in modo che la vostra scelta di perni non è deterministico - se lo è, i dati peggiori possono essere abbastanza facilmente generati.

Al che ho risposto:

  • Analisi di algoritmo Ricerca di Hoare Con mediana delle tre partizioni (1997) di P Kirschenhofer, H Prodinger, C Martínez sostiene la vostra tesi (che 'mediana-di-tre' è di tre oggetti casuali).

  • C'è un articolo descritto in portal.acm.org che sta 'The Worst Case permutazione per mediana-di-Three Quicksort' da Hannu Erkiö, pubblicato su The Computer Journal, Vol 27, No 3, 1984. [Aggiornamento 2012-02- 26: Ha ottenuto il testo per l' articolo . Sezione 2 'L'algoritmo' comincia: ' Usando la mediana dei primi, medio e l'ultimo elemento di A [L: R], pareti efficienti in parti uguali dimensioni abbastanza può essere ottenuto in situazioni più pratici. 'In questo modo, si sta discutendo l'approccio di prima mezza ultima Mo3.]

  • Un altro breve articolo che è interessante è da MD McIlroy, "un avversario Killer per Quicksort" , pubblicato nel Software-pratica e l'esperienza, vol. 29 (0), 1-4 (0 1999). Spiega come fare quasi qualsiasi Quicksort comportarsi quadratico.

  • AT & T Bell Labs Tech Journal, ottobre 1984 "Teoria e pratica nella costruzione di un lavoro Sort di routine", afferma "Hoare ha suggerito di partizionamento intorno alla mediana di diverse linee selezionate in modo casuale. Sedgewick [...] raccomandato scegliendo la mediana della prima [. ..] ultima [...] e mezzo". Ciò indica che entrambe le tecniche per 'mediana-di-tre' sono noti nella letteratura. (Aggiornamento 2014/11/23: L'articolo sembra essere disponibile presso IEEE Xplore o da Wiley - se si dispone di appartenenza o sono disposti a pagare una tassa.)

  • 'Engineering A Ordina funzione' da JL Bentley e MD McIlroy, pubblicato nel software pratica e l'esperienza, Vol 23 (11), novembre 1993 va in una vasta discussione dei problemi, e hanno scelto un algoritmo di partizionamento adattivo basato in parte sul dimensioni del set di dati. C'è un sacco di discussioni di trade-off per i vari approcci.

  • Una ricerca su Google per 'mediana-di-tre' funziona piuttosto bene per un ulteriore monitoraggio.

Grazie per l'informazione; Avevo incontrato solo il deterministico 'mediana-di-tre' prima.

Risposto il 02/10/2008 a 20:42
fonte dall'utente

voti
1

Se si ordina una raccolta casuale accessibile (come un array), è in generale migliore per ritirare l'articolo di mezzo fisico. Con questo, se la matrice è tutto pronto ordinato (o quasi ordinato), le due partizioni saranno vicino al pari, e si otterrà la migliore velocità.

Se si ordina qualcosa con accesso solo lineare (come un-lista collegata), allora è meglio scegliere la prima voce, perché è l'elemento più veloce per l'accesso. Qui, tuttavia, se la lista è già ordinato, sei fregato - una partizione sarà sempre nullo, e l'altro avere tutto, producendo il momento peggiore.

Tuttavia, per una lista collegata, raccogliendo qualcosa oltre la prima, sarà solo peggiorare le cose. E scegliere l'elemento centrale in una lista elencata, dovreste fare un passo attraverso di essa in ogni fase di separazione - l'aggiunta di un O (/ 2 N) operazione che viene effettuata logN volte fare il tempo totale O (1,5 N * log N) e che, se sappiamo per quanto tempo la lista è, prima di cominciare - di solito non così avremmo dovuto fare un passo tutto il percorso attraverso di contarli, poi passo a metà per trovare il mezzo, poi passo attraverso un terza volta per fare la partizione attuale: O (2.5N * log N)

Risposto il 02/10/2008 a 20:42
fonte dall'utente

voti
1

E 'del tutto dipende da come i dati vengono ordinati per cominciare. Se si pensa che sarà pseudo-casuale allora la cosa migliore è quella di scegliere o una selezione casuale o scegliere la metà.

Risposto il 02/10/2008 a 20:46
fonte dall'utente

voti
16

Eh, ho solo insegnato questa classe.

Ci sono diverse opzioni.
Semplice: Scegli il primo o l'ultimo elemento della gamma. (cattiva sull'ingresso parzialmente ordinato) Meglio: Selezionare la voce nel mezzo della gamma. (meglio sull'ingresso parzialmente ordinato)

Tuttavia, raccogliendo ogni elemento arbitrario corre il rischio di mal partizionamento della matrice di dimensione n in due array di dimensione 1 e n-1. Se si fa abbastanza spesso, la vostra quicksort corre il rischio di diventare O (n ^ 2).

Un miglioramento che ho visto è raccogliere mediana (primo, ultimo, a metà); Nel peggiore dei casi, può ancora andare a O (n ^ 2), ma probabilisticamente, questo è un caso raro.

Per la maggior parte dei dati, raccogliendo il primo o l'ultimo è sufficiente. Ma, se si scopre che si sta eseguendo in caso peggiore scenari spesso (ingresso parzialmente ordinato), la prima opzione sarebbe quella di scegliere il valore centrale (che è un buon statisticamente perno per i dati parzialmente ordinati).

Se non sei ancora incorrere in problemi, poi andare via mediana.

Risposto il 02/10/2008 a 20:46
fonte dall'utente

voti
8

Mai e poi mai scegliere un perno fisso - questo può essere attaccato a sfruttare del vostro algoritmo caso peggiore O (n ^ 2) tempo di esecuzione, che è solo in cerca di guai. caso runtime peggiore di quicksort verifica quando partizionamento risultati in una matrice di 1 dell'elemento, ed una matrice di n-1 elementi. Supponiamo di scegliere il primo elemento come la partizione. Se qualcuno nutre un array per l'algoritmo che è in ordine decrescente, il vostro primo perno sarà il più grande, in modo da tutto il resto nella matrice si sposterà a sinistra di esso. Poi, quando si ricorsione, il primo elemento sarà il più grande di nuovo, quindi ancora una volta si mette tutto a sinistra di esso, e così via.

Una tecnica migliore è la mediana-di-3 metodo, in cui si sceglie tre elementi in modo casuale, e scegliere il mezzo. Voi sapete che l'elemento che si sceglie non sarà il primo o l'ultimo, ma anche, per il teorema del limite centrale, la distribuzione del elemento centrale sarà normale, il che significa che si tende verso la metà (e quindi , n lg n tempo).

Se si vuole assolutamente garantire O (nlgn) runtime per l'algoritmo, il metodo colonne-of-5 per trovare la mediana di un array viene eseguito in O (n), il che significa che l'equazione di ricorrenza per Quicksort nel peggiore dei casi sarà essere T (n) = O (n) (per la mediana) + O (n) (partizione) + 2T (n / 2) (Recurse a sinistra ea destra.) Con il teorema, questo è O (n lg n) . Tuttavia, il fattore costante sarà enorme, e se peggiore performance caso è la preoccupazione principale, utilizzare un merge sort, invece, che è solo un po 'più lento rispetto Quicksort, in media, e garantisce O (nlgn) il tempo (e sarà molto più veloce di questo quicksort mediana zoppo).

Spiegazione della mediana della Algorithm mediane

Risposto il 25/10/2008 a 22:50
fonte dall'utente

voti
5

Non cercare di ottenere troppo intelligenti e combinare strategie di articolazione. Se combinato mediana di 3 con perno a caso scegliendo il mediano del primo, ultimo e un caso indice in mezzo, allora sarete ancora vulnerabile a molte delle distribuzioni che inviano mediana di 3 quadratica (così la sua realtà peggio plain perno casuale)

Ad esempio una distribuzione organo a canne (1,2,3 ... N / 2..3,2,1) prima e l'ultima saranno entrambe 1 e l'indice casuale sarà un numero maggiore di 1, prendendo la mediana dà 1 ( sia prima o l'ultima) e si ottiene un partizionamento extermely sbilanciato.

Risposto il 26/10/2008 a 04:54
fonte dall'utente

voti
1

E 'più facile per rompere il quicksort in tre sezioni facendo questo

  1. funzione di scambio o elemento di dati di swap
  2. La funzione di partizione
  3. Elaborazione delle partizioni

E 'solo un po' più inefficent di una funzione a lungo, ma è molto più facile da capire.

Codice segue:

/* This selects what the data type in the array to be sorted is */

#define DATATYPE long

/* This is the swap function .. your job is to swap data in x & y .. how depends on
data type .. the example works for normal numerical data types .. like long I chose
above */

void swap (DATATYPE *x, DATATYPE *y){  
  DATATYPE Temp;

  Temp = *x;        // Hold current x value
  *x = *y;          // Transfer y to x
  *y = Temp;        // Set y to the held old x value
};


/* This is the partition code */

int partition (DATATYPE list[], int l, int h){

  int i;
  int p;          // pivot element index
  int firsthigh;  // divider position for pivot element

  // Random pivot example shown for median   p = (l+h)/2 would be used
  p = l + (short)(rand() % (int)(h - l + 1)); // Random partition point

  swap(&list[p], &list[h]);                   // Swap the values
  firsthigh = l;                                  // Hold first high value
  for (i = l; i < h; i++)
    if(list[i] < list[h]) {                 // Value at i is less than h
      swap(&list[i], &list[firsthigh]);   // So swap the value
      firsthigh++;                        // Incement first high
    }
  swap(&list[h], &list[firsthigh]);           // Swap h and first high values
  return(firsthigh);                          // Return first high
};



/* Finally the body sort */

void quicksort(DATATYPE list[], int l, int h){

  int p;                                      // index of partition 
  if ((h - l) > 0) {
    p = partition(list, l, h);              // Partition list 
    quicksort(list, l, p - 1);        // Sort lower partion
    quicksort(list, p + 1, h);              // Sort upper partition
  };
};
Risposto il 10/03/2011 a 03:19
fonte dall'utente

voti
0

Idealmente il perno deve essere il valore centrale dell'intero array. Ciò consentirà di ridurre le probabilità di ottenere performance peggiore caso.

Risposto il 17/04/2013 a 15:57
fonte dall'utente

voti
-1

In un'implementazione realmente ottimizzato, il metodo per la scelta del perno dovrebbe dipendere dalla dimensione della matrice - per una vasta gamma, si paga per passare più tempo a scegliere un buon pivot. Senza fare un'analisi completa, direi "middle di O (log (n)) Elementi" è un buon inizio, e questo ha il vantaggio di non richiedere alcun ulteriore memoria: Utilizzare tail-call sulla partizione più grande e in- posto partizionamento, usiamo lo stesso O (log (n)) di memoria supplementare a quasi ogni fase dell'algoritmo.

Risposto il 08/10/2013 a 20:50
fonte dall'utente

voti
0

complessità di sorta pratica varia notevolmente con la selezione del valore di perno. per esempio, se si sceglie sempre come primo elemento di un perno, la complessità dell'algoritmo diventa come peggiore di O (n ^ 2). qui è un metodo intelligente per scegliere perno elemento- 1. scegliere il primo, metà, ultimo elemento dell'array. 2. confrontare questi tre numeri e trovare il numero che è maggiore di uno e più piccolo rispetto agli altri cioè mediana. 3. rendere questo elemento come elemento di rotazione.

scegliendo il perno con questo metodo divide l'array in quasi due metà e quindi la complessità riduce a O (nlog (n)).

Risposto il 05/12/2013 a 06:05
fonte dall'utente

voti
0

In media, mediana di 3 è un bene per le piccole n. Mediana di 5 è un po 'meglio per i più grandi n. Il ninther, che è la "mediana di tre mediane di tre" è ancora meglio per molto grande n.

Più alto si va con il campionamento del migliore si ottiene al crescere di n, ma il miglioramento rallenta drasticamente verso il basso come si aumenta i campioni. E si incorre l'overhead di campionamento e l'ordinamento dei campioni.

Risposto il 19/10/2016 a 10:04
fonte dall'utente

voti
0

Mi consiglia di utilizzare l'indice di mezzo, come si può calcolare facilmente.

È possibile calcolare che arrotondando (Array.length / 2).

Risposto il 09/08/2017 a 01:29
fonte dall'utente

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more