89 votes

Algorithme : méthode efficace pour supprimer les entiers en double d'un tableau

J'ai eu ce problème lors d'un entretien avec Microsoft.

Étant donné un tableau d'entiers aléatoires, écrivez un algorithme en C qui supprime qui supprime les nombres dupliqués et renvoie les nombres uniques du tableau original. tableau original.

Par exemple, l'entrée : {4, 8, 4, 1, 1, 2, 9} Sortie : {4, 8, 1, 2, 9, ?, ?}

Une mise en garde s'impose : l'algorithme attendu ne devrait pas exiger que le tableau soit trié en premier. Et lorsqu'un élément a été supprimé, les éléments suivants doivent également être décalés vers l'avant. De toute façon, la valeur des éléments à la queue du tableau où les éléments ont été décalés vers l'avant est négligeable.

Mise à jour : Le résultat doit être renvoyé dans le tableau d'origine et la structure de données auxiliaire (par exemple, hashtable) ne doit pas être utilisée. Cependant, je suppose que la préservation de l'ordre n'est pas nécessaire.

Mise à jour2 : Pour ceux qui se demandent pourquoi ces contraintes peu pratiques, il s'agissait d'une question d'entretien et toutes ces contraintes sont discutées pendant le processus de réflexion pour voir comment je peux proposer des idées différentes.

136voto

ejel Points 1293

Une solution suggérée par ma copine est une variante du tri par fusion. La seule modification est que pendant l'étape de fusion, il suffit de ne pas tenir compte des valeurs dupliquées. Cette solution serait également O(n log n). Dans cette approche, le tri et la suppression de la duplication sont combinés ensemble. Cependant, je ne suis pas sûr que cela fasse une différence.

47voto

dsimcha Points 32831

J'ai déjà publié ce message sur SO, mais je le reproduis ici parce que c'est plutôt cool. Il utilise le hachage, en construisant quelque chose comme un ensemble de hachage en place. Il est garanti d'être O(1) dans l'espace axillaire (la récursion est un appel de queue), et est typiquement O(N) de complexité en temps. L'algorithme est le suivant :

  1. Prenez le premier élément du tableau, ce sera la sentinelle.
  2. Réorganisez le reste du tableau, autant que possible, de façon à ce que chaque élément se trouve à la position correspondant à son hachage. Au fur et à mesure de cette étape, des doublons seront découverts. Mettez-les à la valeur de sentinelle.
  3. Déplace tous les éléments pour lesquels l'index est égal au dièse au début du tableau.
  4. Déplace tous les éléments qui sont égaux à sentinel, sauf le premier élément du tableau, à la fin du tableau.
  5. Ce qui reste entre les éléments correctement hachés et les éléments en double sera les éléments qui n'ont pas pu être placés dans l'index correspondant à leur hachage à cause d'une collision. La récursion permet de traiter ces éléments.

On peut montrer que c'est O(N) à condition qu'il n'y ait pas de scénario pathologique dans le hachage : même s'il n'y a pas de doublons, environ 2/3 des éléments seront éliminés à chaque récursion. Chaque niveau de récursion est O(n) où petit n est la quantité d'éléments restants. Le seul problème est qu'en pratique, c'est plus lent qu'un tri rapide quand il y a peu de doublons, c'est-à-dire beaucoup de collisions. Cependant, lorsqu'il y a une grande quantité de doublons, il est étonnamment rapide.

Edit : Dans les implémentations actuelles de D, hash_t est de 32 bits. Tout ce qui concerne cet algorithme suppose qu'il y aura très peu, voire aucune, collision de hachage dans un espace complet de 32 bits. Les collisions peuvent, cependant, se produire fréquemment dans l'espace modulo. Cependant, cette hypothèse sera vraisemblablement vraie pour tout ensemble de données de taille raisonnable. Si la clé est inférieure ou égale à 32 bits, elle peut être son propre hachage, ce qui signifie qu'une collision dans l'espace 32 bits complet est impossible. Si elle est plus grande, on ne peut tout simplement pas en placer suffisamment dans l'espace d'adressage de la mémoire 32 bits pour que cela pose un problème. Je suppose que hash_t sera augmenté à 64 bits dans les implémentations 64 bits de D, où les ensembles de données peuvent être plus grands. De plus, si cela s'avérait être un problème, on pourrait changer la fonction de hachage à chaque niveau de récursion.

Voici une implémentation dans le langage de programmation D :

void uniqueInPlace(T)(ref T[] dataIn) {
    uniqueInPlaceImpl(dataIn, 0);
}

void uniqueInPlaceImpl(T)(ref T[] dataIn, size_t start) {
    if(dataIn.length - start < 2)
        return;

    invariant T sentinel = dataIn[start];
    T[] data = dataIn[start + 1..$];

    static hash_t getHash(T elem) {
        static if(is(T == uint) || is(T == int)) {
            return cast(hash_t) elem;
        } else static if(__traits(compiles, elem.toHash)) {
            return elem.toHash;
        } else {
            static auto ti = typeid(typeof(elem));
            return ti.getHash(&elem);
        }
    }

    for(size_t index = 0; index < data.length;) {
        if(data[index] == sentinel) {
            index++;
            continue;
        }

        auto hash = getHash(data[index]) % data.length;
        if(index == hash) {
            index++;
            continue;
        }

        if(data[index] == data[hash]) {
            data[index] = sentinel;
            index++;
            continue;
        }

        if(data[hash] == sentinel) {
            swap(data[hash], data[index]);
            index++;
            continue;
        }

        auto hashHash = getHash(data[hash]) % data.length;
        if(hashHash != hash) {
            swap(data[index], data[hash]);
            if(hash < index)
                index++;
        } else {
            index++;
        }
    }

    size_t swapPos = 0;
    foreach(i; 0..data.length) {
        if(data[i] != sentinel && i == getHash(data[i]) % data.length) {
            swap(data[i], data[swapPos++]);
        }
    }

    size_t sentinelPos = data.length;
    for(size_t i = swapPos; i < sentinelPos;) {
        if(data[i] == sentinel) {
            swap(data[i], data[--sentinelPos]);
        } else {
            i++;
        }
    }

    dataIn = dataIn[0..sentinelPos + start + 1];
    uniqueInPlaceImpl(dataIn, start + swapPos + 1);
}

20voto

Byju Points 122

Une mise en œuvre plus efficace

int i, j;

/* new length of modified array */
int NewLength = 1;

for(i=1; i< Length; i++){

   for(j=0; j< NewLength ; j++)
   {

      if(array[i] == array[j])
      break;
   }

   /* if none of the values in index[0..j] of array is not same as array[i],
      then copy the current value to corresponding new position in array */

  if (j==NewLength )
      array[NewLength++] = array[i];
}

Dans cette implémentation, il n'est pas nécessaire de trier le tableau. De même, si un élément en double est trouvé, il n'est pas nécessaire de décaler tous les éléments suivants d'une position.

La sortie de ce code est un tableau[] avec la taille NewLength

Ici, nous partons du 2ème élément du tableau et nous le comparons avec tous les éléments du tableau jusqu'à ce tableau. Nous utilisons une variable d'index supplémentaire 'NewLength' pour modifier le tableau d'entrée. La variable NewLength est initialisée à 0.

L'élément du tableau [1] sera comparé au tableau [0]. S'ils sont différents, alors la valeur du tableau [NewLength] sera modifiée avec le tableau [1] et incrémentera NewLength. S'ils sont identiques, NewLength ne sera pas modifié.

Donc si nous avons un tableau [1 2 1 3 1], alors

Dans le premier passage de la boucle 'j', array[1] (2) sera comparé à array0, puis 2 sera écrit dans array[NewLength] = array[1]. donc le tableau sera [1 2] puisque NewLength = 2

Au deuxième passage de la boucle 'j', le tableau[2] (1) sera comparé au tableau0 et au tableau1. Ici, puisque array[2] (1) et array0 sont identiques, la boucle va s'interrompre. donc le tableau sera [1 2] puisque NewLength = 2

et ainsi de suite

19voto

mocj Points 1008

Pourquoi pas :

void rmdup(int *array, int length)
{
    int *current , *end = array + length - 1;

    for ( current = array + 1; array < end; array++, current = array + 1 )
    {
        while ( current <= end )
        {
            if ( *current == *array )
            {
                *current = *end--;
            }
            else
            {
                current++;
            }
        }
    }
}

Devrait être O(n^2) ou moins.

19voto

carl Points 25879

Si vous recherchez la meilleure notation O, alors trier le tableau avec un tri O(n log n) puis effectuer une traversée O(n) peut être la meilleure solution. Sans tri, vous êtes à la recherche de O(n^2).

Edit : si vous ne faites que des entiers, vous pouvez aussi faire un tri radix pour obtenir O(n).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X