Il semble que le type Radix ait une très bonne performance moyenne de cas, c'est-à-dire O (kN) : http://en.wikipedia.org/ wiki/Radix_sort
mais il semble que la plupart des gens utilisent toujours le tri rapide, n'est-ce pas?
Le tri Radix est plus difficile à généraliser que la plupart des autres algorithmes de tri. Il nécessite des clés de taille fixe et un moyen standard de briser les clés en morceaux. Ainsi, il ne trouve jamais son chemin dans les bibliothèques.
Modifié selon vos commentaires:
Les autres réponses ici sont horribles, elles ne donnent pas d'exemples de moment où radix sort est en fait utilisé.
Un exemple est lors de la création d'un "tableau de suffixes" à l'aide de l'algorithme asymétrique DC3 (Kärkkäinen-Sanders-Burkhardt). L'algorithme n'est linéaire en temps que si l'algorithme de tri est linéaire et le tri radix est nécessaire et utile ici car les clés sont courtes par construction (3 tuples d'entiers).
Sauf si vous avez une énorme liste ou des clés extrêmement petites, log (N) est généralement plus petit que k, il est rarement beaucoup plus élevé. Ainsi, le choix d'un algorithme de tri à usage général avec des performances moyennes de cas O (N log N) n'est pas nécessairement pire que l'utilisation du tri radix.
Correction: Comme @Mehrdad l'a souligné dans les commentaires, l'argument ci-dessus n'est pas valable: soit la taille de la clé est constante, alors le tri radix est O (N), soit la taille de la clé est k, puis quicksort est O (k N log N). Donc, en théorie, le tri radix a vraiment un meilleur temps d'exécution asymptotique.
En pratique, les temps d'exécution seront dominés par des termes comme:
tri radix: c1 k N
tri rapide: c2 k N log (N)
où c1 >> c2, car "extraire" des bits d'une clé plus longue est généralement une opération coûteuse impliquant des décalages de bits et des opérations logiques (ou au moins un accès à la mémoire non aligné), tandis que les processeurs modernes peuvent comparer les clés avec 64, 128 ou même 256 bits en une seule opération. Donc, pour de nombreux cas courants, à moins que N ne soit gigantesque, c1 sera plus grand que c2 log (N)
lorsque n> 128, nous devons utiliser RadixSort
lors du tri int32s, je choisis radix 256, donc k = log (256, 2 ^ 32) = 4, ce qui est beaucoup plus petit que log (2, n)
et dans mon test, le tri radix est 7 fois plus rapide que quicksort dans le meilleur des cas.
public class RadixSort {
private static final int radix=256, shifts[]={8,16,24}, mask=radix-1;
private final int bar[]=new int[radix];
private int s[] = new int[65536];//不使用额外的数组t,提高cpu的cache命中率
public void ensureSort(int len){
if(s.length < len)
s = new int[len];
}
public void sort(int[] a){
int n=a.length;
ensureSort(n);
for(int i=0;i<radix;i++)bar[i]=0;
for(int i=0;i<n;i++)bar[a[i]&mask]++;//bar存放了桶内元素数量
for(int i=1;i<radix;i++)bar[i]+=bar[i-1];//bar存放了桶内的各个元素在排序结果中的最大下标+1
for(int i=0;i<n;i++)s[--bar[a[i]&mask]]=a[i];//对桶内元素,在bar中找到下标x=bar[slot]-1, 另s[x]=a[i](同时--bar[slot]将下标前移,供桶内其它元素使用)
for(int i=0;i<radix;i++)bar[i]=0;
for(int i=0;i<n;i++)bar[(s[i]>>8)&mask]++;
for(int i=1;i<radix;i++)bar[i]+=bar[i-1];
for(int i=n-1;i>=0;i--)a[--bar[(s[i]>>8)&mask]]=s[i];//同一个桶内的元素,低位已排序,而放入t中时是从t的大下标向小下标放入的,所以应该逆序遍历s[i]来保证原有的顺序不变
for(int i=0;i<radix;i++)bar[i]=0;
for(int i=0;i<n;i++)bar[(a[i]>>16)&mask]++;
for(int i=1;i<radix;i++)bar[i]+=bar[i-1];
for(int i=n-1;i>=0;i--)s[--bar[(a[i]>>16)&mask]]=a[i];//同一个桶内的元素,低位已排序,而放入t中时是从t的大下标向小下标放入的,所以应该逆序遍历s[i]来保证原有的顺序不变
for(int i=0;i<radix;i++)bar[i]=0;
for(int i=0;i<n;i++)bar[(s[i]>>24)&mask]++;
for(int i=129;i<radix;i++)bar[i]+=bar[i-1];//bar[128~255]是负数,比正数小
bar[0] += bar[255];
for(int i=1;i<128;i++)bar[i]+=bar[i-1];
for(int i=n-1;i>=0;i--)a[--bar[(s[i]>>24)&mask]]=s[i];//同一个桶内的元素,低位已排序,而放入t中时是从t的大下标向小下标放入的,所以应该逆序遍历s[i]来保证原有的顺序不变
}
}
Le tri Radix prend du temps O (k * n). Mais vous devez vous demander ce qui est K. K est le "nombre de chiffres" (un peu simpliste mais fondamentalement quelque chose comme ça).
Alors, combien de chiffres avez-vous? Assez réponse, plus que log (n) (log utilisant la "taille des chiffres" comme base) qui fait l'algorithme Radix O (n log n).
Pourquoi donc? Si vous avez moins de log (n) chiffres, alors vous avez moins de n nombres possibles. Par conséquent, vous pouvez simplement utiliser "count sort" qui prend O(n) fois (comptez juste combien de chaque nombre vous avez). Donc je suppose que vous avez plus de k> log (n) chiffres ...
C'est pourquoi les gens n'utilisent pas autant le tri Radix. Bien qu'il y ait des cas où cela vaut la peine de l'utiliser, dans la plupart des cas, le tri rapide est beaucoup mieux.
k = "longueur de la valeur la plus longue dans le tableau à trier"
n = "longueur du tableau"
O (k * n) = "pire cas en cours"
k * n = n ^ 2 (si k = n)
ainsi, lorsque vous utilisez le tri Radix, assurez-vous que "le plus long entier est plus court que la taille du tableau" ou vice versa. Ensuite, vous allez battre Quicksort!
L'inconvénient est: la plupart du temps, vous ne pouvez pas garantir la taille des entiers, mais si vous avez une plage fixe de nombres, le tri par radix devrait être la voie à suivre.
Voici un lien qui compare quicksort et radixsort:
Le tri radix est-il plus rapide que le tri rapide pour les tableaux entiers? (oui, 2-3x)
Voici un autre lien qui analyse les temps d'exécution de plusieurs algorithmes:
Ce qui est plus rapide sur les mêmes données; un tri O(n) ou un tri O(nLog(n))?
Réponse: Cela dépend. Cela dépend de la quantité de données triées. Cela dépend du matériel sur lequel il est exécuté, et cela dépend de la mise en œuvre des algorithmes.
Le tri Radix n'est pas un tri basé sur la comparaison et ne peut trier que les types numériques tels que les entiers (y compris les adresses de pointeurs) et les virgules flottantes, et il est un peu difficile de prendre en charge de manière portative les virgules flottantes.
C'est probablement parce qu'il a une gamme d'applicabilité si étroite que de nombreuses bibliothèques standard choisissent de l'omettre. Il ne peut même pas vous permettre de fournir votre propre comparateur, car certaines personnes pourraient ne même pas vouloir trier les entiers directement autant que d'utiliser les entiers comme indices vers autre chose à utiliser comme clé de tri, par exemple Les types basés sur la comparaison permettent toute cette flexibilité, il s'agit donc probablement de préférer une solution généralisée répondant à 99% des besoins quotidiens des gens au lieu de se démener pour répondre à ce 1%.
Cela dit, malgré l'applicabilité étroite, dans mon domaine, je trouve plus d'utilisation pour les sortes de radix que les introsorts ou les quicksorts. Je suis dans ce 1% et je ne travaille presque jamais avec, disons, des clés de chaîne, mais je trouve souvent des cas d'utilisation pour les nombres qui bénéficient d'un tri. C'est parce que ma base de code tourne autour des indices des entités et des composants (système entité-composant) ainsi que des choses comme les maillages indexés et il y a beaucoup de données numériques.
En conséquence, le tri radix devient utile pour toutes sortes de choses dans mon cas. Un exemple courant dans mon cas est l'élimination des index en double. Dans ce cas, je n'ai pas vraiment besoin de trier les résultats, mais souvent un tri radix peut éliminer les doublons plus rapidement que les alternatives.
Un autre consiste à trouver, disons, une répartition médiane pour un arbre kd le long d'une dimension donnée. Là-bas, le tri des valeurs à virgule flottante du point pour une dimension donnée me donne une position médiane rapidement en temps linéaire pour diviser le nœud d'arbre.
Une autre consiste à trier en profondeur les primitives de niveau supérieur par z
pour une transparence alpha semi-correcte si nous n'allons pas le faire dans un shader de frag. Cela s'applique également aux interfaces graphiques et aux logiciels de graphiques vectoriels pour les éléments d'ordre z.
Un autre est l'accès séquentiel compatible avec le cache à l'aide d'une liste d'index. Si les indices sont parcourus plusieurs fois, cela améliore souvent les performances si je les trie à l'avance pour que la traversée se fasse dans un ordre séquentiel plutôt que dans un ordre aléatoire. Ce dernier pourrait zigzaguer d'avant en arrière dans la mémoire, expulsant les données des lignes de cache uniquement pour recharger la même région de mémoire à plusieurs reprises dans la même boucle. Lorsque je trie les index avant de les accéder à plusieurs reprises, cela cesse et je peux réduire considérablement les erreurs de cache. C'est en fait mon utilisation la plus courante pour les sortes de radix et c'est la clé pour que mon ECS soit compatible avec le cache lorsque les systèmes veulent accéder à des entités avec deux ou plusieurs composants.
Dans mon cas, j'ai une sorte de radix multithread que j'utilise assez souvent. Quelques repères:
--------------------------------------------
- test_mt_sort
--------------------------------------------
Sorting 1,000,000 elements 32 times...
mt_radix_sort: {0.234000 secs}
-- small result: [ 22 48 59 77 79 80 84 84 93 98 ]
std::sort: {1.778000 secs}
-- small result: [ 22 48 59 77 79 80 84 84 93 98 ]
qsort: {2.730000 secs}
-- small result: [ 22 48 59 77 79 80 84 84 93 98 ]
Je peux faire la moyenne de quelque chose comme 6-7 ms pour trier un million de numéros une fois sur mon matériel dinky, ce qui n'est pas aussi rapide que je le souhaiterais car 6-7 millisecondes peuvent encore être remarquées par les utilisateurs parfois dans des contextes interactifs, mais toujours un tout beaucoup mieux que 55-85 ms comme dans le cas de std::sort
de C++ ou de qsort
de C qui conduirait certainement à des hoquets très évidents dans les fréquences d'images. J'ai même entendu parler de personnes implémentant des sortes de radix à l'aide de SIMD, bien que je ne sache pas comment elles ont géré cela. Je ne suis pas assez intelligent pour proposer une telle solution, bien que même mon petit genre naïf de radix se débrouille assez bien par rapport aux bibliothèques standard.
Un exemple serait lorsque vous triez un très grand ensemble ou tableau d'entiers. Un tri radix et tout autre type de distribution sont extrêmement rapides car les éléments de données sont principalement mis en file d'attente dans un tableau de files d'attente (max 10 files d'attente pour un tri radix LSD) et remappés à un emplacement d'index différent des mêmes données d'entrée à trier. Il n'y a pas de boucles imbriquées, de sorte que l'algorithme a tendance à se comporter de manière plus linéaire lorsque le nombre d'entiers d'entrée de données à trier augmente considérablement. Contrairement à d'autres méthodes de tri, comme la méthode bubbleSort extrêmement inefficace, le tri radix n'implémente pas d'opérations de comparaison pour trier. C'est juste un processus simple de remappage d'entiers à différentes positions d'index jusqu'à ce que l'entrée soit finalement triée. Si vous souhaitez tester par vous-même un tri de radix LSD, j'en ai écrit un et stocké sur github qui peut être facilement testé sur un js ide en ligne tel que le sandbox de codage javascript éloquent. N'hésitez pas à jouer avec et à voir comment il se comporte avec différents nombres de n. J'ai testé jusqu'à 900 000 entiers non triés avec un temps d'exécution <300 ms. Voici le lien si vous souhaitez jouer avec.
https://Gist.github.com/StBean/4af58d09021899f14dfa585df6c86df6