web-dev-qa-db-fra.com

Quicksort: Choisir le pivot

Lors de la mise en œuvre de Quicksort, vous devez notamment choisir un pivot. Mais lorsque je regarde un pseudo-code comme celui ci-dessous, on ne voit pas comment je devrais choisir le pivot. Premier élément de la liste? Autre chose?

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

Quelqu'un peut-il m'aider à comprendre le concept de pivot et à déterminer si différents scénarios nécessitent des stratégies différentes?.

103
Jacob T. Nielsen

Choisir un pivot aléatoire minimise les risques de rencontrer le cas le plus défavorable de O (n2) performance (le fait de choisir toujours le premier ou le dernier aurait des conséquences défavorables sur les données presque triées ou triées inversement). Le choix de l'élément central serait également acceptable dans la majorité des cas.

De plus, si vous implémentez ceci vous-même, certaines versions de l’algorithme fonctionnent sur place (c’est-à-dire sans créer deux nouvelles listes et les concaténer ensuite).

81
Kip

Cela dépend de vos besoins. Le choix aléatoire d'un pivot rend plus difficile la création d'un ensemble de données générant des performances O (N ^ 2). La "médiane de trois" (premier, dernier, milieu) est également un moyen d'éviter les problèmes. Méfiez-vous des performances relatives des comparaisons, cependant; si vos comparaisons sont coûteuses, Mo3 fait plus de comparaisons que de choisir (une seule valeur de pivot) au hasard. Les enregistrements de base de données peuvent être coûteux à comparer.


Mise à jour: Tirer des commentaires en réponse.

mdkess affirmé:

"Médiane de 3" n'est PAS le premier dernier dernier milieu. Choisissez trois index aléatoires et prenez la valeur moyenne. L’essentiel est de vous assurer que votre choix de pivots n’est pas déterministe. Si tel est le cas, les données du pire cas peuvent être assez facilement générées.

A quoi j'ai répondu:

  • Analyse de l'algorithme de recherche de Hoare avec une partition médiane de trois (1997) de P Kirschenhofer, H Prodinger, C Martínez appuie votre affirmation (cette "médiane de trois" est composée de trois éléments aléatoires).

  • Il y a un article décrit sur portal.acm.org qui parle de "La permutation du pire cas pour une médiane de trois Quicksort" de Hannu Erkiö, publiée dans The Computer Journal, Vol 27, No 3, 1984. . [Update 2012-02-26: Vous avez le texte pour le article . La section 2 'L'algorithme' commence: ' En utilisant la médiane des premier, deuxième et dernier éléments de A [L: R], il est possible d'obtenir des partitions efficaces en parties de tailles à peu près égales: la plupart des situations pratiques. 'Ainsi, il discute de l’approche premier-milieu-dernier de Mo3.]

  • M. D. McIlroy, "Un adversaire meurtrier pour Quicksort" , publié dans Software-Practice and Experience, Vol. 29 (0), 1-4 (0 1999). Il explique comment presque tous les Quicksort se comportent de manière quadratique.

  • Le journal technique d'AT & T Bell Labs, octobre 1984, indique "Théorie et pratique pour la construction d'une routine de tri active", suggérant de séparer Hoare de plusieurs lignes choisies au hasard. Sedgewick recommanda de choisir la médiane du premier [. ..] dernier [...] et milieu ". Cela indique que les deux techniques de "médiane sur trois" sont connues dans la littérature. (Mise à jour du 26/11/2014: l'article semble être disponible à l'adresse IEEE Xplore ou à partir de Wiley - si vous êtes membre ou êtes prêt à payer des frais.)

  • 'Engineering a Sort Function' de JL Bentley et MD McIlroy, publié dans Software Practice and Experience, Vol 23 (11), novembre 1993, donne lieu à une discussion approfondie des problèmes et choisit une méthode adaptative. algorithme de partitionnement basé en partie sur la taille de l'ensemble de données. Il y a beaucoup de discussions sur les compromis pour différentes approches.

  • Une recherche sur Google "median-of-three" fonctionne assez bien pour le suivi.

Merci pour l'information; Je n'avais rencontré auparavant que la "médiane de trois" déterministe.

55
Jonathan Leffler

Heh, je viens d'enseigner cette classe.

Il y a plusieurs options.
Simple: Choisissez le premier ou le dernier élément de la plage. (mauvais sur une entrée partiellement triée) Mieux: Choisissez l’élément au milieu de la plage. (mieux sur une entrée partiellement triée)

Cependant, choisir n'importe quel élément arbitraire risque de mal partitionner le tableau de taille n en deux tableaux de taille 1 et n-1. Si vous le faites assez souvent, votre tri rapide risque de devenir O (n ^ 2).

Une amélioration que j'ai constatée est le choix de la médiane (premier, dernier, moyen); Dans le pire des cas, il peut toujours aller à O (n ^ 2), mais il est probable que ce soit un cas rare.

Pour la plupart des données, choisir le premier ou le dernier est suffisant. Toutefois, si vous rencontrez souvent les pires scénarios (entrée partiellement triée), la première option consiste à choisir la valeur centrale (qui est un pivot statistique correct pour les données partiellement triées).

Si vous rencontrez toujours des problèmes, choisissez la voie médiane.

17
Chris Cudmore

Ne choisissez jamais un pivot fixe - il peut être attaqué pour exploiter le pire temps d'exécution O (n ^ 2) de votre algorithme, qui ne demande que des problèmes. Le pire scénario de Quicksort se produit lorsque le partitionnement génère un tableau de 1 élément et un tableau de n-1 éléments. Supposons que vous choisissiez le premier élément comme partition. Si quelqu'un alimente votre algorithme par un ordre décroissant, votre premier pivot sera le plus grand. Par conséquent, tout le reste du tableau se déplacera à gauche. Ensuite, lorsque vous récidiverez, le premier élément sera à nouveau le plus important. Une fois de plus, vous mettez tout à gauche, et ainsi de suite.

Une meilleure technique est la méthode de la médiane de 3, où vous choisissez trois éléments au hasard et choisissez le milieu. Vous savez que l'élément que vous choisissez ne sera ni le premier ni le dernier, mais aussi, selon le théorème de la limite centrale, la distribution de l'élément central sera normale, ce qui signifie que vous tendrez vers le milieu (et donc , n lg n time).

Si vous voulez absolument garantir O(nlgn) exécution pour l'algorithme, la méthode colonnes-sur-5 pour trouver la médiane d'un tableau s'exécute dans O(n) temps, ce qui signifie que l'équation de récurrence pour quicksort dans le pire des cas sera T(n) = O(n) (trouver la médiane ) + O(n) (partition) + 2T (n/2) (récidive gauche et droite.) Par le théorème maître, il s'agit de O (n lg n). Cependant, la constante facteur sera énorme, et si le pire des cas est votre principale préoccupation, utilisez plutôt un type de fusion, qui est un peu plus lent en moyenne que quicksort en moyenne, et garantit O(nlgn) time ( et sera beaucoup plus rapide que ce tri rapide médian boiteux).

Explication de l'algorithme de la médiane des médianes

9
mindvirus

N'essayez pas d'être trop intelligent et ne combinez pas de stratégies pivotantes. Si vous combinez la médiane de 3 avec un pivot aléatoire en choisissant la médiane de la première, de la dernière et un indice aléatoire au milieu, vous serez toujours vulnérable à de nombreuses distributions qui envoient une médiane de 3 quadratique (elle est donc pire que pivot aléatoire simple)

Par exemple, une distribution d'organes de tuyaux (1,2,3 ... N/2..3,2,1) sera première et dernière sera 1 et l'indice aléatoire sera un nombre supérieur à 1, la médiane donnant 1 ( premier ou dernier) et vous obtenez un partitionnement extrêmement déséquilibré.

6
paperhorse

Il est plus facile de diviser le tri rapide en trois sections.

  1. Echange ou échange de fonction d'élément de données
  2. La fonction de partition
  3. Traitement des partitions

C'est seulement un peu plus inefficace qu'une longue fonction mais c'est beaucoup plus facile à comprendre.

Le code suit:

/* 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
  };
};
1
Uglybb

Si vous triez une collection accessible au hasard (comme un tableau), il est généralement préférable de choisir l'élément du milieu physique. Avec cela, si le tableau est tout prêt (ou presque), les deux partitions seront presque égales et vous obtiendrez la meilleure vitesse.

Si vous triez quelque chose avec uniquement un accès linéaire (comme une liste chaînée), il est préférable de choisir le premier élément, car il s'agit de l'élément le plus rapide à accéder. Ici, cependant, si la liste est déjà triée, vous êtes foutu - une partition sera toujours nulle et l'autre aura tout, produisant le pire moment.

Cependant, pour une liste chaînée, choisir autre chose que la première ne fera qu'empirer les choses. Il choisit l'élément du milieu dans une liste, vous devrez donc le parcourir à chaque étape de la partition - en ajoutant une opération O(N/2) qui est exécutée, logN fois, ce qui donne le total temps O (1,5 N * log N) et si nous savons combien de temps la liste est longue avant de commencer - généralement nous ne le faisons pas, nous devrions donc faire tout le chemin pour les compter, puis faire un demi-tour pour trouver le milieu, puis passez une troisième fois pour créer la partition actuelle: O (2.5N * log N)

1
James Curran

Cela dépend entièrement de la manière dont vos données sont triées. Si vous pensez que ce sera pseudo-aléatoire, alors votre meilleure option est de choisir une sélection aléatoire ou de choisir le milieu.

1
Joe Phillips

Je recommande d'utiliser l'index moyen, car il peut être calculé facilement.

Vous pouvez le calculer en arrondissant (array.length/2).

0
Milesman34

En moyenne, la médiane de 3 est bonne pour les petits n. La médiane de 5 est un peu meilleure pour les plus grands n. Le ninther, qui est la "médiane de trois médianes sur trois" est encore meilleur pour les très grands n.

Plus vous échantillonnez haut, mieux vous augmentez avec n, mais l'amélioration ralentit considérablement à mesure que vous augmentez les échantillons. Et vous induisez des frais généraux d'échantillonnage et de tri des échantillons.

0
S0lo

La complexité du tri rapide varie considérablement en fonction de la sélection de la valeur de pivot. Par exemple, si vous choisissez toujours le premier élément comme pivot, la complexité de l'algorithme devient aussi pire que O (n ^ 2). Voici une méthode intelligente pour choisir un élément pivot: 1. Choisissez le premier, le milieu et le dernier élément du tableau. 2. Comparez ces trois nombres et trouvez le nombre qui est supérieur à un et inférieur aux autres, c'est-à-dire la médiane. 3. faire de cet élément un élément de pivot.

le choix du pivot par cette méthode divise le tableau en presque deux moitiés et la complexité se réduit donc à O (nlog (n)).

0
vivek

Idéalement, le pivot devrait être la valeur centrale de tout le tableau. Cela réduira les chances d'obtenir les performances les plus défavorables.

0
Faizan