Quelle est la médiane de trois stratégies pour sélectionner la valeur pivot en tri rapide?
Je le lis sur le web, mais je n'ai pas pu le comprendre exactement. Et aussi comment c'est mieux que le tri rapide randomisé.
La médiane de trois vous permet de regarder les premier, milieu et dernier éléments du tableau, et de choisir la médiane de ces trois éléments comme pivot.
Pour obtenir le "plein effet" de la médiane de trois, il est également important de trier ces trois éléments, pas seulement d'utiliser la médiane comme pivot - cela n'affecte pas ce qui est choisi comme pivot dans l'itération en cours, mais peut/affectera ce qui est utilisé comme pivot dans le prochain appel récursif, ce qui aide à limiter le mauvais comportement pour quelques commandes initiales (celui qui s'avère particulièrement mauvais dans de nombreux cas est un tableau trié, sauf pour avoir le plus petit élément à l'extrémité haute du tableau (ou le plus grand élément à l'extrémité basse). Par exemple:
Par rapport à la sélection aléatoire du pivot:
Ce deuxième point mérite sans doute un peu plus d'explications. Si vous avez utilisé le générateur de nombres aléatoires (Rand()
) évident, il est assez facile (dans de nombreux cas, de toute façon) pour quelqu'un d'organiser les éléments afin qu'il choisisse continuellement de mauvais pivots. Cela peut être une préoccupation sérieuse pour quelque chose comme un serveur Web qui peut trier des données qui ont été saisies par un attaquant potentiel, qui pourrait monter une attaque DoS en obligeant votre serveur à perdre beaucoup de temps à trier les données. Dans un cas comme celui-ci, vous pourriez utiliser une graine vraiment aléatoire, ou vous pouvez inclure votre propre PRNG au lieu d'utiliser Rand () - ou vous utilisez la médiane de trois, ce qui présente également les autres avantages mentionnés.
En revanche, si vous utilisez un générateur suffisamment aléatoire (par exemple, un générateur matériel ou un cryptage en mode compteur), il est probablement plus difficile de forcer un mauvais cas que pour une médiane de trois sélections. Dans le même temps, atteindre ce niveau d'aléatoire a généralement un peu de frais généraux, donc à moins que vous ne vous attendiez vraiment à être attaqué dans ce cas, cela ne vaut probablement pas la peine (et si vous le faites, cela vaut probablement au moins alternative qui garantit le pire des cas O (N log N), comme un tri par fusion ou un tri en tas.
Une implémentation de Median of Three que j'ai trouvé fonctionne bien dans mes tris rapides.
(Python)
# Get the median of three of the array, changing the array as you do.
# arr = Data Structure (List)
# left = Left most index into list to find MOT on.
# right = Right most index into list to find MOT on
def MedianOfThree(arr, left, right):
mid = (left + right)/2
if arr[right] < arr[left]:
Swap(arr, left, right)
if arr[mid] < arr[left]:
Swap(arr, mid, left)
if arr[right] < arr[mid]:
Swap(arr, right, mid)
return mid
# Generic Swap for manipulating list data.
def Swap(arr, left, right):
temp = arr[left]
arr[left] = arr[right]
arr[right] = temp
Cette stratégie consiste à choisir trois nombres de façon déterministe ou aléatoire, puis à utiliser leur médiane comme pivot.
Ce serait mieux car cela réduit la probabilité de trouver de "mauvais" pivots.
Pensez simple ... Python ....
def plus grand (a, b): #Trouvez le plus grand de deux nombres ... si a> b: retournez un sinon: retourner b def plus grand (a, b, c): #Trouver le plus grand des trois nombres ... retourner plus grand (a, plus grand (b, c) ) médiane def (a, b, c): #Just dance! x = plus grand (a, b, c) if x == a : retour plus grand (b, c) si x == b: retour plus grand (a, c) sinon: retour plus grand (un B)
Le common/Vanilla quicksort sélectionne comme pivot l'élément le plus à droite. Ceci a pour conséquence qu'il présente des performances pathologiques O (N²) pour un certain nombre de cas. Notamment les collections triées et inversées. Dans les deux cas, l'élément le plus à droite est le pire élément possible à sélectionner comme pivot. Le pivot m'est idéalement pensé au milieu du cloisonnement. Le partitionnement est censé diviser les données avec le pivot en deux sections, une section basse et une section haute. La section basse étant inférieure au pivot, la section haute étant plus élevée.
Médiane de trois sélection du pivot:
Les pathologies communes O (N²) des entrées triées/triées inversées en sont atténuées. Il est toujours facile de créer des entrées pathologiques pour la médiane de trois. Mais c'est une utilisation construite et malveillante. Pas un ordre naturel.
randomisé pivot:
S'il est aléatoire, cela ne présente pas de comportement pathologique O (N²). Le pivot aléatoire est généralement très probable en termes de calcul pour un tri générique et en tant que tel indésirable. Et si ce n'est pas aléatoire (c'est-à-dire srand (0);, Rand (), prévisible et vulnérable au même exploit O (N²) que ci-dessus.
Notez que le pivot aléatoire ne fait pas bénéficier de la sélection de plusieurs éléments. Principalement parce que l'effet de la médiane est déjà intrinsèque et qu'une valeur aléatoire est plus exigeante en calcul que la commande de deux éléments.
Nous pouvons comprendre la stratégie de la médiane de trois par un exemple, supposons qu'on nous donne un tableau:
[8, 2, 4, 5, 7, 1]
L'élément le plus à gauche est donc 8
, et l'élément le plus à droite est 1
. L'élément central est 4
, puisque pour tout tableau de longueur 2k, nous choisirons l'élément k th.
Et puis nous trions ces trois éléments dans un ordre croissant ou décroissant, ce qui nous donne:
[1, 4, 8]
Ainsi, la médiane est 4
. Et nous utilisons 4
comme pivot.
Côté implémentation, on peut avoir:
// javascript
function findMedianOfThree(array) {
var len = array.length;
var firstElement = array[0];
var lastElement = array[len-1];
var middleIndex = len%2 ? (len-1)/2 : (len/2)-1;
var middleElement = array[middleIndex];
var sortedArray = [firstElement, lastElement, middleElement].sort(function(a, b) {
return a < b; //descending order in this case
});
return sortedArray[1];
}
Une autre façon de l'implémenter est inspirée de @kwrl, et je voudrais l'expliquer un peu plus clairement:
// javascript
function findMedian(first, second, third) {
if ((second - first) * (third - first) < 0) {
return first;
}else if ((first - second) * (third - second) < 0) {
return second;
}else if ((first - third)*(second - third) < 0) {
return third;
}
}
function findMedianOfThree(array) {
var len = array.length;
var firstElement = array[0];
var lastElement = array[len-1];
var middleIndex = len%2 ? (len-1)/2 : (len/2)-1;
var middleElement = array[middleIndex];
var medianValue = findMedian(firstElement, lastElement, middleElement);
return medianValue;
}
Considérez la fonction findMedian
, le premier élément sera retourné uniquement lorsque second Element > first Element > third Element
et third Element > first Element > second Element
, et dans les deux cas: (second - first) * (third - first) < 0
, le même raisonnement s'applique aux deux autres cas.
L'avantage d'utiliser la deuxième implémentation est qu'elle pourrait avoir un meilleur temps d'exécution.
Réfléchissez plus vite ... Exemple C ...
int medianThree(int a, int b, int c) {
if ((a > b) != (a > c))
return a;
else if ((b > a) != (b > c))
return b;
else
return c;
}
Cela utilise XOR
comme opérateur. Vous lirez donc:
a
est-il supérieur à exclusivement l'un des autres? return a
b
est-il supérieur à exclusivement l'un des autres? return b
return c
L'approche médiane est plus rapide car elle entraînerait un partitionnement plus homogène dans le tableau, car le partitionnement est basé sur la valeur pivot.
Dans le pire des cas, avec un choix aléatoire ou un choix fixe, vous partitionneriez chaque tableau en un tableau contenant uniquement le pivot et un autre tableau avec le reste, ce qui conduirait à une complexité O (n²).
En utilisant l'approche médiane, vous vous assurez que cela ne se produira pas, mais au lieu de cela, vous introduisez un surcoût pour le calcul de la médiane.
Benchmarks les résultats montrent que XOR
est 24 fois plus rapide que Bigger
même si j'ai optimisé un peu plus: