web-dev-qa-db-fra.com

Tri Radix vs tri Comptage vs tri Bucket. Quelle est la différence?

Je lis les définitions de radix, de comptage et de types de seaux et il semble que tous ne soient que le code ci-dessous:

public static void sort(int[] a, int maxVal){
    int [] bucket=new int[maxVal+1];

    for (int i=0; i<bucket.length; i++){
        bucket[i]=0;
    }

    for (int i=0; i<a.length; i++){
        bucket[a[i]]++;
    }

    int outPos=0;
    for (int i=0; i<bucket.length; i++){
        for (int j=0; j<bucket[i]; j++){
            a[outPos++]=i;
        }
    }
}

Je sais que je ne peux pas avoir raison, alors qu'est-ce qui me manque? Affichez le code si vous pensez que cela peut aider à expliquer en Java ou C.

52
good_evening

Commençons par réécrire votre code en C, car C est plus familier à expliquer. Permet donc de rappeler votre code avec quelques commentaires:

int
counting_sort(int a[], int a_len, int maxVal)
{
  int i, j, outPos = 0;
  int bucket_len = maxVal+1;
  int bucket[bucket_len]; /* simple bucket structure */

  memset(bucket, 0, sizeof(int) * bucket_len);

  /* one loop bucket processing */
  for (i = 0; i < a_len; i++)
    {
      bucket[a[i]]++; /* simple work with buckets */
    }

  for (i=0; i < bucket_len; i++)
    {
      for (j = 0; j < bucket[i]; j++)
        {
          a[outPos++] = i;
        }
    }

  return 0;
}

Permet maintenant d'offrir à ce gars des données réalistes:

[126, 348, 343, 432, 316, 171, 556, 223, 670, 201]

En sortie, nous avons

[126, 171, 201, 223, 316, 343, 348, 432, 556, 670]

Il semble que tout va bien? Pas encore. Regardons maxVal. Il s'agit de 670 (!) Pour trier un tableau de 10 éléments, nous avons utilisé ici un tableau de 670 éléments, principalement des zéros. Terriblement. Pour gérer ce problème de tri par comptage, nous avons deux possibilités de généralisation:

1) Première façon - de trier les chiffres. C'est ce qu'on appelle le tri radix. Permet d'afficher du code, en essayant de le rapprocher le plus possible du code de tri de comptage. Regardez à nouveau les commentaires:

int
radix_sort(int a[], int a_len, int ndigits)
{
  int i;
  int b[a_len];
  int expn = 1;

  /* additional loop for digits */
  for (i = 0; i != ndigits; ++i)
    {
      int j;
      int bucket[10] = {0}; /* still simple buckets */

      /* bucket processing becomes tricky */
      for (j = 0; j != a_len; ++j)
        bucket[ a[j] / expn % 10 ]++;

      for (j = 1; j != 10; ++j)
        bucket[j] += bucket[j - 1];

      for (j = a_len - 1; j >= 0; --j)
        b[--bucket[a[j] / expn % 10]] = a[j];

      for (j = 0; j != a_len; ++j)
        a[j] = b[j];

      expn *= 10;
    }
}

Nous échangeons un multiplicateur près de N pour mémoire. Profit? Peut être. Mais dans certains cas, le multiplicateur proche de N est très important. Le programme, travailler une journée et travailler une semaine sont très différents de la vue des utilisateurs même si les deux fonctionnent respectivement 1 * O (N) et 7 * O (N). Et nous arrivons donc à une deuxième généralisation:

2) Deuxième façon - pour rendre les seaux plus sophistiqués. C'est ce qu'on appelle le seau-tri.

Commençons à nouveau avec du code. Je préfère plus de code avant les arguments philosophiques. Regardez toujours les commentaires, ils sont essentiels.

int
bucket_sort(int a[], int a_len, int maxVal)
{
  int i, aidx;

  typedef struct tag_list {
    int elem;
    struct tag_list *next;
  } list_t, *list_p;

  list_p bucket[10] = {0}; /* sophisticated buckets */

  /* one loop simple processing with one more inner loop 
    to get sorted buckets (insert-sort on lists, Cormen-style) */
  for (i = 0; i != a_len; ++i)
    {
      int bnum = (10 * a[i]) / maxVal;
      list_p bptr = bucket[bnum];
      list_p belem = malloc(sizeof(list_t));
      belem->elem = a[i];
      if (bptr == 0)
        {
          bucket[bnum] = belem;
          belem->next = 0;
          continue;
        }
      else if (a[i] <= bptr->elem)
        {
          belem->next = bptr;
          bucket[bnum] = belem;
          continue;
        }
      else
        {
          while (bptr != 0)
            {
              if ((bptr->elem <= a[i]) && ((bptr->next == 0) || (bptr->next->elem > a[i])))
                {
                  belem->next = bptr->next;
                  bptr->next = belem;
                  break;
                }
               bptr = bptr->next;
            }
         }
    }

  /* one loop (looks as two) to get all back */
  aidx = 0;

  for (i = 0; i != 10; ++i)
    {
      list_p bptr = bucket[i];
      while (bptr)
        {
          list_p optr = bptr;
          a[aidx] = bptr->elem;
          aidx += 1;
          bptr = bptr->next;
          free(optr);
        }
    }

  return 0;
}

Alors qu'avons-nous ici? Nous échangeons une structure de compartiment sophistiquée et une exigence de mémoire allouée dynamiquement mais gagnant de la mémoire statique et un multiplicateur proche de N en moyenne.

Maintenant, rappelons ce que nous avons vu dans le code:

  1. Tri par comptage - compartiments simples, traitement simple, surcharge de mémoire
  2. Tri Radix - compartiments simples, traitement sophistiqué, surcharge de vitesse (et toujours besoin de mémoire statique supplémentaire)
  3. Tri des compartiments - compartiments sophistiqués, traitement simple, nécessite une mémoire dynamique, bonne en moyenne

Les tris radix et bucket sont donc deux généralisations utiles du tri par comptage. Ils ont beaucoup en commun avec le comptage et entre eux mais dans tous les cas, nous perdons quelque chose et gagnons quelque chose. Le génie logiciel consiste à trouver un équilibre entre ces opportunités.

61

Tri Radix vs tri Comptage vs tri Bucket. Quelle est la différence?

Le tri par compartiment place les clés ou les éléments à trier dans des compartiments. La façon dont ils sont placés dans les compartiments est arbitraire et peut être des portions d'une clé composite et toute distribution que vous aimez. Les seaux individuels peuvent devoir être triés davantage.

Le tri en mémoire est plus rapide que le tri sur disque. Cependant, si vous avez plus de données que ce qui est en mémoire, vous avez besoin d'une autre option. Ce que vous pouvez faire est un type de compartiment, où les compartiments sont suffisamment petits pour tenir en mémoire. c'est-à-dire qu'il y a un grand nombre d'entrées dans chaque compartiment. Vous pouvez les trier rapidement individuellement.

Le tri Radix est un type spécifique de tri par compartiment. Il commence par les n bits ou les n chiffres supérieurs et peut trier ces compartiments à l'aide d'un tri radix, etc., jusqu'à ce que chaque entrée soit triée.

Compter le tri, c'est comme utiliser le tri radix sauf que vous utilisez la valeur entière. Au lieu d'enregistrer chaque objet, il a un compartiment pour chaque objet et il compte juste le nombre d'occurrences. Cela fonctionne bien lorsque vous avez un nombre limité de clés possibles et que vous avez de nombreux doublons.

14
Peter Lawrey

Selon Geekviewpoint:

Radix: http://www.geekviewpoint.com/Java/sorting/radixsort

Le tri Radix, comme le tri par comptage et le tri par compartiment, est un algorithme basé sur des entiers (c'est-à-dire que les valeurs du tableau d'entrée sont supposées être des entiers). Par conséquent, le tri radix est parmi les algorithmes de tri les plus rapides, en théorie. La distinction particulière pour le tri radix est qu'il crée un seau pour chaque chiffre (c'est-à-dire un chiffre); en tant que tel, similaire au tri par compartiment, chaque compartiment en tri radix doit être une liste évolutive qui peut admettre des clés différentes.

Seau: http://www.geekviewpoint.com/Java/sorting/bucketsort

Le tri par compartiment est en fait très bon étant donné que le tri par comptage est raisonnablement sa limite supérieure. Et compter le tri est très rapide. La distinction particulière pour le tri de compartiment est qu'il utilise une fonction de hachage pour partitionner les clés du tableau d'entrée, de sorte que plusieurs clés peuvent hacher dans le même compartiment. Par conséquent, chaque seau doit effectivement être une liste évolutive; similaire au tri radix.

Comptage: http://www.geekviewpoint.com/Java/sorting/countingsort

La distinction particulière pour le comptage du tri est qu'il crée un compartiment pour chaque valeur et conserve un compteur dans chaque compartiment. Ensuite, chaque fois qu'une valeur est rencontrée dans la collection d'entrée, le compteur approprié est incrémenté. Étant donné que le comptage du tri crée un compartiment pour chaque valeur, une restriction imposante est que la valeur maximale dans le tableau d'entrée soit connue à l'avance.

Ils l'expliquent plus en détail sur leur site.

Éditer:

  • Si vous utilisez le tri radix et que vos nombres sont décimaux, vous avez besoin de 10 compartiments, un pour chaque chiffre de 0 à 9.

  • Si vous utilisez le tri par comptage, vous avez besoin d'un compartiment pour chaque valeur unique dans l'entrée (en fait, vous avez besoin d'un compartiment pour chaque valeur comprise entre 0 et max).

  • Si vous utilisez bucketsort, vous ne savez pas combien de buckets vous utiliserez. Quelle que soit la fonction de hachage que vous utilisez, le nombre de compartiments sera déterminé.

7
kasavbere

Votre code est une variante simple du comptage du tri sans données, juste des clés.

Le tri Radix est basé sur cette méthode. Le problème avec le tri du comptage est la mémoire requise: int [] bucket=new int[maxVal+1];. Radix sort résout ce problème. L'idée est d'utiliser le tri par comptage plusieurs fois, d'abord pour les chiffres inférieurs, puis pour les chiffres supérieurs. Par exemple, pour trier des entiers 32 bits, vous pouvez utiliser:

sort(a, 65535) using lower half as key
sort(a, 65535) using higher half as key

Cela fonctionne, car le tri du comptage est stable - il maintient l'ordre des données avec des clés égales. C'est comme trier dans une feuille de calcul: sort by B; sort by A Vous donne des éléments triés par A et par B lorsque As sont égaux.

Le tri par compartiment est une généralisation du tri par comptage. Vous pouvez l'utiliser pour trier des nombres réels à partir d'une distribution de probabilité prévisible (par exemple, uniforme (0,1)). L'idée est d'utiliser le tri par comptage (en utilisant floor(x*N_BUCKETS) comme clé), puis de ne trier chaque compartiment indépendamment.

6
zch

Voyons d'abord la différence entre Radix Sort et Bucket Sort car c'est généralement une chose déroutante car l'idée semble la même. Ensuite, nous examinons le tri de comptage qui est comme une version principale de ces deux et quels problèmes avec le tri de comptage entraînent l'utilisation des deux autres

Les passes initiales des types Radix et Bucket sont les mêmes.Les éléments sont placés dans 'Buckets', c'est-à-dire 0-10, 11-20, ... et ainsi de suite, en fonction du nombre de chiffres dans le plus grand non, c'est-à-dire le base. Dans l'étape suivante, cependant, les ordres de tri des compartiments augmentent ces "compartiments" et les ajoutent dans un tableau. Cependant, la méthode de tri radix ajoute les compartiments sans autre tri et les "re-buckets" en fonction du deuxième chiffre (dix) des nombres. Par conséquent, le tri Bucket est plus efficace pour les tableaux "Denses", tandis que Radix Sort peut bien gérer les tableaux clairsemés. Eh bien, pensez au type de seau comme ceci

Supposons que vous ayez une liste de n enregistrements contenant chacun une clé de 1 à k (nous généralisons un peu le problème, donc k n'est pas nécessairement égal à n).

Nous pouvons résoudre ce problème en créant un tableau de listes liées. Nous déplaçons chaque enregistrement d'entrée dans la liste à la position appropriée du tableau puis concaténons toutes les listes ensemble dans l'ordre.

 bucket sort(L)
    {
    list Y[k+1]
    for (i = 0; i <= k; i++) Y[i] = empty
    while L nonempty
    {
        let X = first record in L
        move X to Y[key(X)]
    }
    for (i = 0; i <= k; i++)
    concatenate Y[i] onto end of L
    }

Que faire quand k est grand? Pensez à la représentation décimale d'un nombre x = a + 10 b + 100 c + 1000 d + ... où a, b, c etc. tous dans la plage 0..9. Ces chiffres sont facilement assez petits pour effectuer un tri par compartiment.

   radix sort(L):
    {
    bucket sort by a
    bucket sort by b
    bucket sort by c
    ...
    }

ou plus simplement

radix sort(L):
{
while (some key is nonzero)
{
    bucket sort(keys mod 10)
    keys = keys / 10
}
}

Pourquoi faisons-nous d'abord le chiffre le moins important? D'ailleurs, pourquoi faisons-nous plus d'un tri de seaux, puisque le dernier est celui qui met tout en place? Réponse: Si nous essayons de trier les choses à la main, nous avons tendance à faire quelque chose de différent: d'abord faire un tri par compartiment, puis trier récursivement les valeurs partageant un premier chiffre commun. Cela fonctionne, mais est moins efficace car il divise le problème en plusieurs sous-problèmes. En revanche, le tri radix ne sépare jamais la liste; il applique simplement le tri par compartiment plusieurs fois à la même liste. Dans le tri Radix, la dernière passe du tri par seau est celle qui a le plus d'effet sur l'ordre global. Nous voulons donc que ce soit celui qui utilise les chiffres les plus importants. Les passes de tri précédentes ne sont utilisées que pour prendre en charge le cas où deux articles ont la même clé (mod 10) lors de la dernière passe.

Maintenant que nous avons tout cela hors de la façon dont tout tri de comptage fait, il conserve un tableau auxiliaire C avec k éléments, tous initialisés à 0.

Nous effectuons un passage dans le tableau d'entrée A et pour chaque élément i dans A que nous voyons, nous incrémentons C [i] de 1. Après avoir parcouru les n éléments de A et mis à jour C, la valeur à l'indice j de C correspond combien de fois j est apparu dans A. Cette étape prend O(n) temps pour parcourir it A. Une fois que nous avons C, nous pouvons construire la version triée de A en itérant via C et en insérant chaque élément ja total de C [j] fois dans une nouvelle liste (ou A lui-même). L'itération à travers C prend O(k) temps. Le résultat final est un A trié et au total il a mis O (n + k) pour le faire.

L'inconvénient du tri par comptage est qu'il peut ne pas être trop pratique si la plage d'éléments est trop grande. Par exemple, si la plage des n éléments que nous devons trier était de 1 à n 3, la simple création du tableau auxiliaire C prendra O (n ^ 3) et compter le tri sera asymptotiquement pire que le tri par insertion. Cela prend également de l'espace O (n ^ 3) qui est signi fi cativement plus grand que tout espace utilisé par tout autre algorithme de tri que nous avons appris jusqu'à présent. Le tri Radix permet de résoudre ce problème en triant les éléments chiffre par chiffre

Remarque: Sources de réponse et de lectures complémentaires:

http://htmltolatex.sourceforge.net/samples/sample4.html

La première réponse à: Quelle est la différence entre le tri par seau et le tri radix?

3
Slartibartfast

Le tri Radix utilise une forme de tri de comptage comme sous-programme (ok, peut utiliser, mais le plus souvent ce sera le tri de comptage).

Countingsort est une forme spéciale de tri par seau, comme l'a répondu Kasavbere.

Et Bucketsort divise les clés en compartiments, puis trie les compartiments individuellement.

2
kutschkem

Pour trier un tableau à l'aide du tri par nombre:

#define MAX_INPUT 1000

void sort(int arr[100], int n)
{
    static int hash[MAX_INPUT], i, j;

    memset(hash, 0, sizeof hash);

    for (i = 0; i < n; ++i) ++hash[arr[i]];

    j = 0;
    for (i = 0; i < MAX_INPUT; ++i)
        while (hash[i]--)
           arr[j++] = i;
}

Il s'agit simplement de O(MAX_INPUT), triant ainsi en temps linéaire. Pour le tri par seau, c'est très différent. Voici ne implémentation

1
user586399