Comptage de distribution estimé - tri le plus souvent réinventé


Le nombre de sortes plus ou moins différentes les unes des autres est garanti à plus d'une centaine. Parmi eux, il existe des sous-groupes d'algorithmes qui diffèrent au minimum les uns des autres, coïncidant avec une idée principale générale. En fait, au fil des années, différentes personnes reviennent avec les mêmes tris, différant par des détails peu fondamentaux.

Une telle idée algorithmique se retrouve plus souvent que d'autres.

Chaque élément est entré approximativement à cet endroit du tableau où il doit être situé. Il s'avère qu'un tableau presque ordonné . À quel tri par insertions est appliqué (il est le plus efficace pour le traitement de tableaux presque ordonnés) ou les zones non ordonnées locales sont traitées récursivement par le même algorithme.

Logiciel EDISON - développement web
Cet article a été écrit avec le soutien d'EDISON, qui développe une large gamme de solutions pour une variété de tâches: des programmes pour essayer en ligne les vêtements des magasins multimarques au système de transmission LED entre les fleuves et les navires .

Nous aimons la théorie des algorithmes! ;-)
Pour évaluer approximativement où vous voulez placer l'élément, vous devez savoir à quel point il diffère de l'élément moyen du tableau. Pour ce faire, vous devez connaître les valeurs des éléments minimum et maximum, ainsi que la taille du tableau.

Le tableau trié est censé avoir des données vraiment aléatoires. Tous les inventeurs de cette méthode arrivent à la même formule:


k est la place approximative dans le tableau où l'élément A ( i ) doit être
min , max - valeurs des éléments minimum et maximum dans le tableau A
Taille - le nombre d'éléments dans le tableau A

Voici une telle idée générale. Voyons dans quelles variations cet algorithme est né encore et encore.

Tri du roi Salomon :: Tri Salomon



Cette méthode (et son beau nom) a été proposée par l' utilisateur du V2008n il y a environ 5 ans. Tout a son temps, «le temps de disperser les pierres et le temps de collecter les pierres» (les mots du roi Salomon du livre de l'Ecclésiaste) - et dans l'algorithme, c'est exactement ce qui se passe. Tout d'abord, à l'aide de la formule, nous dispersons les éléments aux endroits souhaités dans le tableau. Étant donné que la formule ne donne pas une place exacte, mais approximative, plusieurs éléments proches les uns des autres en valeur revendiquent à la fois certaines positions. Ces groupes d'éléments locaux sont triés par insertions puis assemblés dans l'ordre final.

Tri par interpolation


"Il n'y a rien de nouveau sous le soleil", pour reprendre le même auteur. Wikipedia décrit le tri par interpolation, rappelant étrangement le tri de Salomon. Chaque «tas de pierres» est un petit réseau dynamique supplémentaire, où se trouvent des éléments d'importance similaire. La principale différence est qu'après la «diffusion de pierres», ces groupes locaux d'éléments non triés ne sont pas triés par insertions, mais par tri par interpolation (récursivement ou en boucle).

Un tableau ordonné est un ensemble de données discret qui peut être considéré comme un ensemble fini de valeurs connues d'une certaine fonction inconnue. En fait, une distribution approximative du point de vue des mathématiques de calcul - c'est l'interpolation.

Tri d'interpolation JavaScript - Bouclage
Array.prototype.interpolationSort = function() { var divideSize = new Array(); var end = this.length; divideSize[0] = end; while(divideSize.length > 0) {divide(this);} function divide(A) { var size = divideSize.pop(); var start = end - size; var min = A[start]; var max = A[start]; var temp = 0; for(var i = start + 1; i < end; i++) { if(A[i] < min) { min = A[i]; } else { if(A[i] > max) {max = A[i];} } } if(min == max) { end = end - size; } else { var p = 0; var bucket = new Array(size); for(var i = 0; i < size; i++) {bucket[i] = new Array();} for(var i = start; i < end; i++) { p = Math.floor(((A[i] - min) / (max - min)) * (size - 1)); bucket[p].push(A[i]); } for(var i = 0; i < size; i++) { if(bucket[i].length > 0) { for(var j = 0; j < bucket[i].length; j++) {A[start++] = bucket[i][j];} divideSize.push(bucket[i].length); } } } } }; 

Tri par interpolation JavaScript - version récursive
 Array.prototype.bucketSort = function() { var start = 0; var size = this.length; var min = this[0]; var max = this[0]; for(var i = 1; i < size; i++) { if (this[i] < min) { min = this[i]; } else { if(this[i] > max) {max = this[i];} } } if(min != max) { var bucket = new Array(size); for(var i = 0; i < size; i++) {bucket[i] = new Array();} var interpolation = 0; for(var i = 0; i < size; i++){ interpolation = Math.floor(((this[i] - min) / (max - min)) * (size - 1)); bucket[interpolation].push(this[i]); } for(var i = 0; i < size; i++) { if(bucket[i].length > 1) {bucket[i].bucketSort();} //Recursion for(var j = 0; j < bucket[i].length; j++) {this[start++] = bucket[i][j];} } } }; 

Tri d'histogramme :: Tri d'histogramme


Il s'agit d'une optimisation du tri par interpolation, qui compte le nombre d'éléments appartenant à des groupes locaux non triés. Ce nombre vous permet d'insérer des éléments non triés directement dans le tableau résultant (au lieu de les regrouper dans de petits tableaux séparés).

Tri des barres JavaScript
 Array.prototype.histogramSort = function() { var end = this.length; var sortedArray = new Array(end); var interpolation = new Array(end); var hitCount = new Array(end); var divideSize = new Array(); divideSize[0] = end; while(divideSize.length > 0) {distribute(this);} function distribute(A) { var size = divideSize.pop(); var start = end - size; var min = A[start]; var max = A[start]; for(var i = start + 1; i < end; i++) { if (A[i] < min) { min = A[i]; } else { if (A[i] > max) {max = A[i];} } } if (min == max) { end = end - size; } else { for(var i = start; i < end; i++){hitCount[i] = 0;} for(var i = start; i < end; i++) { interpolation[i] = start + Math.floor(((A[i] - min) / (max - min)) * (size - 1)); hitCount[interpolation[i]]++; } for(var i = start; i < end; i++) { if(hitCount[i] > 0){divideSize.push(hitCount[i]);} } hitCount[end - 1] = end - hitCount[end - 1]; for(var i = end - 1; i > start; i--) { hitCount[i - 1] = hitCount[i] - hitCount[i - 1]; } for(var i = start; i < end; i++) { sortedArray[hitCount[interpolation[i]]] = A[i]; hitCount[interpolation[i]]++; } for(var i = start; i < end; i++) {A[i] = sortedArray[i];} } } }; 

Tri des balises d'interpolation


Afin d'optimiser davantage la surcharge, il est proposé ici de ne pas se souvenir du nombre d'éléments similaires dans les groupes non triés, mais simplement de marquer le début de ces groupes avec des drapeaux True / False. Vrai signifie que le sous-groupe est déjà trié et Faux signifie qu'il ne l'est pas encore.

Tri par interpolation avec balise JavaScript
 Array.prototype.InterpolaionTagSort = function() { var end = this.length; if(end > 1) { var start = 0 ; var Tag = new Array(end); //Algorithm step-1 for(var i = 0; i < end; i++) {Tag[i] = false;} Divide(this); } //Algorithm step-2 while(end > 1) { while(Tag[--start] == false){} //Find the next bucket's start Divide(this); } function Divide(A) { var min = A[start]; var max = A[start]; for(var i = start + 1; i < end; i++) { if(A[i] < min) { min = A[i]; } else { if(A[i] > max ) {max = A[i];} } } if(min == max) { end = start; } else { //Algorithm step-3 Start to be the next bucket's end var interpolation = 0; var size = end - start; var Bucket = new Array(size);//Algorithm step-4 for(var i = 0; i < size; i++) {Bucket[i] = new Array();} for(var i = start; i < end; i++) { interpolation = Math.floor(((A[i] - min) / (max - min)) * (size - 1)); Bucket[interpolation].push(A[i]); } for(var i = 0; i < size; i++) { if(Bucket[i].length > 0) {//Algorithm step-5 Tag[start] = true; for(var j = 0; j < Bucket[i].length; j++) {A[start++] = Bucket[i][j];} } } } }//Algorithm step-6 }; 

Tri des balises d'interpolation (sur place)


Si les valeurs des éléments du tableau ne sont pas répétées et uniformément réparties (grosso modo - si les données sous forme triée sont quelque chose comme une progression arithmétique), alors vous pouvez trier en une seule passe, en les triant correctement, sans déplacer les éléments vers des tableaux intermédiaires.

Trier par interpolation avec des étiquettes (en place) en JavaScript
 Array.prototype.InPlaceTagSort = function() { var n = this.length; var Tag = new Array(n); for(i = 0; i < n; i++) {Tag[i] = false;} var min = this[0]; var max = this[0]; for(i = 1; i < n; i++) { if(this[i] < min) { min = this[i]; } else { if(this[i] > max) {max = this[i];} } } var p = 0; var temp = 0; for(i = 0; i < n; i++) { while(Tag[i] == false) { p = Math.floor(((this[i] - min) / (max - min)) * (n - 1)); temp = this[i]; this[i] = this[p]; this[p] = temp; Tag[p] = true; } } }; 

Tri Flash :: Flashsort


Il était une fois, j'ai écrit sur le tri, inventé par le professeur de biophysique Neubert en 1998.

Le professeur a suggéré de répartir les éléments dans plusieurs classes distinctes (l'appartenance à une classe est déterminée par la taille de l'élément). Dans cet esprit, la formule ressemble à ceci:


Au lieu de Size (taille du tableau), la formule indique m - le nombre de classes selon lesquelles nous distribuons les éléments du tableau. La formule ne calcule pas la clé dans le tableau où l'élément doit être jeté, mais le numéro de classe auquel l'élément appartient.

Ce tri n'est pas mauvais en ce qu'il est plus économique sur la mémoire supplémentaire. La redistribution des éléments a lieu sur place. Seule la localisation des classes est stockée séparément (enfin, si vous regardez sous un angle différent, le nombre d'éléments appartenant à une classe particulière est stocké séparément).

Eh bien, le reste est la même chanson.


Tri Flash en Java
 /** * FlashSort.java - integer version * Translation of Karl-Dietrich Neubert's algorithm into Java by * Rosanne Zhang */ class FlashSort { static int n; static int m; static int[] a; static int[] l; /* constructor @param size of the array to be sorted */ public static void flashSort(int size) { n = size; generateRandomArray(); long start = System.currentTimeMillis(); partialFlashSort(); long mid = System.currentTimeMillis(); insertionSort(); long end = System.currentTimeMillis(); // print the time result System.out.println("Partial flash sort time : " + (mid - start)); System.out.println("Straight insertion sort time: " + (end - mid)); } /* Entry point */ public static void main(String[] args) { int size = 0; if (args.length == 0) { usage(); System.exit(1); } try { size = Integer.parseInt(args[0]); } catch (NumberFormatException nfe) { usage(); System.exit(1); } FlashSort.flashSort(size); } /* Print usage */ private static void usage() { System.out.println(); System.out.println("Usage: java FlashSort n "); System.out.println(" n is size of array to sort"); } /* Generate the random array */ private static void generateRandomArray() { a = new int[n]; for(int i=0; i < n; i++) { a[i] = (int)(Math.random() * 5 * n); } m = n / 20; l = new int[m]; } /* Partial flash sort */ private static void partialFlashSort() { int i = 0, j = 0, k = 0; int anmin = a[0]; int nmax = 0; for(i=1; i < n; i++) { if (a[i] < anmin) anmin=a[i]; if (a[i] > a[nmax]) nmax=i; } if(anmin == a[nmax]) return; double c1 = ((double)m - 1) / (a[nmax] - anmin); for(i=0; i < n; i++) { k= (int) (c1 * (a[i] - anmin)); l[k]++; } for(k=1; k < m; k++) { l[k] += l[k - 1]; } int hold = a[nmax]; a[nmax] = a[0]; a[0] = hold; int nmove = 0; int flash; j = 0; k = m - 1; while(nmove < n - 1) { while(j > (l[k] - 1)) { j++; k = (int) (c1 * (a[j] - anmin)); } flash = a[j]; while(!(j == l[k])) { k = (int) (c1 * (flash - anmin)); hold = a[l[k] - 1]; a[l[k] - 1] = flash; flash = hold; l[k]--; nmove++; } } } /* Straight insertion sort */ private static void insertionSort() { int i, j, hold; for(i = a.length - 3; i >= 0; i--) { if(a[i + 1] < a[i]) { hold = a[i]; j = i; while (a[j + 1] < hold) { a[j] = a[j + 1]; j++; } a[j] = hold; } } } /* For checking sorting result and the distribution */ private static void printArray(int[] ary) { for(int i=0; i < ary.length; i++) { if((i + 1) % 10 ==0) { System.out.println(ary[i]); } else { System.out.print(ary[i] + " "); } System.out.println(); } } } 

Tri approximatif :: Tri Proxmap


Ce tri est le plus ancien de ceux mentionnés ici; il a été introduit en 1980 par le professeur Thomas Standish de l'Université de Californie. En apparence, cela semble être sensiblement différent, mais si vous regardez de près, tout est pareil.

L'algorithme fonctionne avec un concept tel qu'un hit - un certain nombre dont la valeur est proche d'un élément du tableau.
Pour déterminer si un élément de tableau est un hit, une fonction d'approximation est appliquée à l'élément.

Le professeur Standish a trié des tableaux de nombres réels. La fonction d'approximation consistait à arrondir les nombres réels à un entier.
C'est, par exemple, si le tableau contient des éléments 2.8, 2, 2.1, 2.6, etc. alors un coup sûr pour ces numéros sera égal.



Procédure générale:

  1. Nous appliquons une fonction d'approximation à chaque élément, déterminant quel hit correspond à l'élément suivant.
  2. Ainsi, pour chaque hit, on peut calculer le nombre d'éléments correspondant à ce hit.
  3. Connaissant le nombre d'éléments pour tous les hits, nous déterminons la localisation des hits (bordures à gauche) dans le tableau.
  4. Connaissant la localisation des hits, nous déterminons la localisation de chaque élément.
  5. Après avoir déterminé la localisation de l'élément, nous essayons de l'insérer à sa place dans le tableau. Si la place est déjà prise, alors on déplace les voisins vers la droite (si l'élément est plus petit qu'eux) pour faire de la place à l'élément. Ou à droite nous insérons l'élément lui-même (s'il est plus que des voisins).

En tant que fonction d'approximation, vous pouvez affecter n'importe laquelle en fonction de la nature générale des données du tableau. Dans les implémentations modernes de ce tri, les hits sont généralement déterminés non pas en mordant la partie fractionnaire, mais en utilisant notre formule préférée.

Tri par approximation JavaScript
 Array.prototype.ProxmapSort = function() { var start = 0; var end = this.length; var A2 = new Array(end); var MapKey = new Array(end); var hitCount = new Array(end); for(var i = start; i < end; i++) {hitCount[i] = 0;} var min = this[start]; var max = this[start]; for (var i = start+1; i < end; i++){ if (this[i] < min) { min = this[i]; } else { if(this[i] > max) {max = this[i];} } } //Optimization 1.Save the MapKey[i]. for (var i = start; i < end; i++) { MapKey[i] = Math.floor(((this[i] - min ) / (max - min)) * (end - 1)); hitCount[MapKey[i]]++; } //Optimization 2.ProxMaps store in the hitCount. hitCount[end-1] = end - hitCount[end - 1]; for(var i = end-1; i > start; i--){ hitCount[i-1] = hitCount[i] - hitCount[i - 1]; } //insert A[i]=this[i] to A2 correct position var insertIndex = 0; var insertStart = 0; for(var i = start; i < end; i++){ insertIndex = hitCount[MapKey[i]]; insertStart = insertIndex; while(A2[insertIndex] != null) {insertIndex++;} while(insertIndex > insertStart && this[i] < A2[insertIndex - 1]) { A2[insertIndex] = A2[insertIndex - 1]; insertIndex--; } A2[insertIndex] = this[i]; } for(var i = start; i < end; i++) {this[i] = A2[i];} }; 

Tri par insertion de tri par hachage :: Tri par hachage


Eh bien, nous terminerons notre examen avec l'algorithme que l' évaluateur bobbyKdas a suggéré il y a 6 ans. Il s'agit d'un algorithme hybride dans lequel, en plus de la distribution et des insertions, la fusion est également ajoutée.

  1. Le tableau est récursivement divisé en deux, jusqu'à ce qu'à un certain stade, les tailles des demi-sous-réseaux atteignent la taille minimale (l'auteur ne compte pas plus de 500 éléments).
  2. Au niveau de récursivité le plus bas, un algorithme familier est appliqué à chaque demi-sous-tableau - en utilisant la même formule, une distribution approximative se produit à l'intérieur du sous-tableau, avec un tri par insertions des sections locales non triées.
  3. Après l'arrangement des deux moitiés des sous-réseaux, ils fusionnent.
  4. Le point 3 (fusion de demi-sous-matrices triées) est répété lors de la remontée le long des niveaux de récursivité jusqu'au sommet, lorsque le tableau d'origine est combiné à partir de deux moitiés.

Trier par insertion de hachage en Java
 import java.util.Arrays; import java.util.Date; import java.util.Random; public class HashSort { //    static int SOURCELEN = 1000000; int source[] = new int[SOURCELEN]; //        int quick[] = new int[SOURCELEN]; //     static int SORTBLOCK = 500; static int k = 3; //  static int TMPLEN = (SOURCELEN < SORTBLOCK * k) ? SORTBLOCK * k : SOURCELEN; int tmp[] = new int[TMPLEN]; //    static int MIN_VAL = 10; static int MAX_VAL = 1000000; int minValue = 0; int maxValue = 0; double hashKoef = 0; //      public void randomize() { int i; Random rnd = new Random(); for(i=0; i<SOURCELEN; i++) { int rndValue = MIN_VAL + ((int)(rnd.nextDouble()*((double)MAX_VAL-MIN_VAL))); source[i] = rndValue; } } //         - public void findMinMax(int startIndex, int endIndex) { int i; minValue = source[startIndex]; maxValue = source[startIndex]; for(i=startIndex+1; i<=endIndex; i++) { if( source[i] > maxValue) { maxValue = source[i]; } if( source[i] < minValue) { minValue = source[i]; } } hashKoef = ((double)(k-1)*0.9)*((double)(endIndex-startIndex)/((double)maxValue-(double)minValue)); } // (  - )      public void stickParts(int startIndex, int mediana, int endIndex) { int i=startIndex; int j=mediana+1; int k=0; //      -    while(i<=mediana && j<=endIndex) { if(source[i]<source[j]) { tmp[k] = source[i]; i++; } else { tmp[k] = source[j]; j++; } k++; } //     -      if( i>mediana ) { while(j<=endIndex) { tmp[k] = source[j]; j++; k++; } } //     -      if(j>endIndex) { while(i<=mediana) { tmp[k] = source[i]; i++; k++; } } System.arraycopy(tmp, 0, source, startIndex, endIndex-startIndex+1); } //        //         boolean shiftRight(int index) { int endpos = index; while( tmp[endpos] != 0) { endpos++; if(endpos == TMPLEN) return false; } while(endpos != index ) { tmp[endpos] = tmp[endpos-1]; endpos--; } tmp[endpos] = 0; return true; } //-    public int hash(int value) { return (int)(((double)value - (double)minValue)*hashKoef); } //        public void insertValue(int index, int value) { int _index = index; //  ,    //            - while(tmp[_index] != 0 && tmp[_index] <= value) { _index++; } //       ,    if( tmp[_index] != 0) { shiftRight(_index);//      } tmp[_index] = value;//  -   } //        public void extract(int startIndex, int endIndex) { int j=startIndex; for(int i=0; i<(SORTBLOCK*k); i++) { if(tmp[i] != 0) { source[j] = tmp[i]; j++; } } } //   public void clearTMP() { if( tmp.length < SORTBLOCK*k) { Arrays.fill(tmp, 0); } else { Arrays.fill(tmp, 0, SORTBLOCK*k, 0); } } //  public void hashingSort(int startIndex, int endIndex) { //1.          findMinMax(startIndex, endIndex); //2.    clearTMP(); //3.       - for(int i=startIndex; i<=endIndex; i++) { insertValue(hash(source[i]), source[i]); } //4.         extract(startIndex, endIndex); } //         public void sortPart(int startIndex, int endIndex) { //    500,   - if((endIndex - startIndex) <= SORTBLOCK ) { hashingSort(startIndex, endIndex); return; } //  > 500         int mediana = startIndex + (endIndex - startIndex) / 2; sortPart(startIndex, mediana);//    sortPart(mediana+1, endIndex);//    stickParts(startIndex, mediana, endIndex);//   -   } //       public void sort() { sortPart(0, SOURCELEN-1); } public static void main(String[] args) { HashSort hs = new HashSort(); hs.randomize(); hs.sort(); } } 

La formule elle-même est appelée une fonction de hachage, et le tableau auxiliaire pour la distribution approximative est appelé une table de hachage.

Les références


Interpolation et histogramme , Flash , Proxmap

Salomon , Table de hachage , Flash

Articles de série:



Le tri par approximation est apparu dans l'application AlgoLab Excel (dans ce cas, dans le tableau initial non trié, la partie fractionnaire aléatoire est ajoutée aux entiers). Salomon et Flash existent depuis longtemps, mais n'ont pas encore implémenté d'interpolation, de hachage et d'histogramme.

Source: https://habr.com/ru/post/fr478654/


All Articles