72 votes

La meilleure façon de trouver une intersection entre deux tableaux ?

J'ai été confronté à ce problème à plusieurs reprises dans diverses situations. Il est générique à tous les langages de programmation bien que je sois à l'aise avec C ou Java.

Considérons deux tableaux (ou collections) :

char[] A = {'a', 'b', 'c', 'd'};
char[] B = {'c', 'd', 'e', 'f'};

Comment obtenir les éléments communs entre les deux tableaux dans un nouveau tableau ? Dans ce cas, l'intersection des tableaux A et B est la suivante char[] c = {'c', 'd'} .

Je veux éviter l'itération répétée d'un tableau à l'intérieur d'un autre tableau, ce qui augmentera le temps d'exécution de (longueur de A fois la longueur de B). augmenterait le temps d'exécution de (longueur de A fois la longueur de B), ce qui est excessif dans le cas de grands tableaux.

Est-il possible de faire un seul passage dans chaque tableau pour obtenir les éléments communs ?

23 votes

Triez d'abord les tableaux. Vous n'aurez alors besoin que d'une seule passe.

0 votes

Comme indiqué ci-dessus, triez les deux tableaux, et à partir de là, c'est vraiment facile.

0 votes

Considérons que le tri ne peut pas être mis en œuvre dans ce cas (je dis cela parce que dans la plupart des cas quotidiens, il faudra plus de temps pour trier les types de données non primitifs ou les classes, ce qui supprimerait l'objectif de la passe unique).

109voto

codaddict Points 154968
foreach element e in array A
    insert e into hash table H

foreach element e in array B
    if H contains e 
        print e

Cet algorithme est O(N) dans le temps et O(N) dans l'espace.

Pour éviter l'espace supplémentaire, vous pouvez utiliser l'approche basée sur le tri.

5 votes

@Yola Il n'y a pas de solution plus rapide que O(n) ; il ne peut y en avoir.

0 votes

Cette solution n'est pas toujours O(N)

5 votes

Notez qu'il y a toujours un problème avec les doublons (la façon de le gérer n'était pas spécifiée dans la question), mais souvent vous voulez imprimer chaque élément min{#occurances(A),#occurances(B)} ou une seule fois, alors que cette solution les imprime #occurances(B) temps

33voto

Jakub Zaverka Points 5909

La limite inférieure de l'efficacité est O(n) - il faut au moins lire tous les éléments. Il existe donc plusieurs approches :

L'approche la plus simple et la plus stupide

Recherche de chaque élément du tableau 1 dans le tableau 2. Complexité en temps O(n^2).

Approche du tri

Vous devez trier uniquement le tableau 1, puis rechercher les éléments du tableau 2 en utilisant la recherche binaire. Complexité en temps : tri O(nlogn), recherche O(n * logn) = O(nlogn), total O(nlogn).

Approche par hachage

Créer une table de hachage à partir d'un tableau de un éléments. Recherchez les éléments du deuxième tableau dans la table de hachage. La complexité temporelle dépend de la fonction de hachage. Vous pouvez obtenir O(1) pour les recherches dans le cas optimal (tous les éléments auront une valeur de hachage différente), mais O(n) dans le pire des cas (tous les éléments auront la même valeur de hachage). Complexité totale en temps : O(n^x), où x est un facteur d'efficacité de la fonction de hachage (entre 1 et 2).

Certaines fonctions de hachage sont garanties pour construire une table sans collisions. Mais la construction ne prend plus strictement O(1) temps pour chaque élément. Elle sera O(1) dans la plupart des cas, mais si la table est pleine ou si une collision est rencontrée, alors la table doit être réorganisée - ce qui prend O(n) temps. Cela n'arrive pas si souvent, beaucoup moins fréquemment que les ajouts propres. Ainsi, la complexité temporelle AMORTISSÉE est de O(1). Nous ne nous soucions pas du fait que certains ajouts prennent O(n) de temps, tant que la majorité des ajouts prend O(1) de temps.

Mais même ainsi, dans un cas extrême, la table doit être remaniée à chaque insertion, donc la complexité temporelle stricte serait O(n^2).

1 votes

Il n'est pas nécessaire d'effectuer un nouveau hachage puisque la longueur du tableau peut être précalculée, et vous pouvez créer la table de hachage de taille n * LF^-1 , donde LF est votre facteur de charge prédéterminé. La complexité est O(1) par opération pour tout LF < 1 et non O(n^LF) parce que le nombre attendu de lectures dont vous aurez besoin est de E= 1 + 1*LF + 1*LF^2 + ... + 1*LF^n < CONSTANT (somme de séries géométriques), donc chaque op est O(1) . Cela dit, le pire cas des tables de hachage est toujours O(n) par opération, mais le cas moyen sera O(1)

0 votes

De plus, l'approche du tri peut être effectuée en O(NlogM) où N est la longueur du réseau le plus long et M la longueur du réseau le plus court.

1 votes

@amit Je suis d'accord, mais le rehash sera toujours nécessaire s'il y a une collision dans la table.

20voto

Mike Points 16224

Il y a quelques méthodes dans certains langages que je connais qui font exactement ce que vous voulez, avez-vous envisagé de regarder certaines de ces implémentations ?

PHP - array_intersect()

$array1 = array("a" => "green", "red", "blue");
$array2 = array("b" => "green", "yellow", "red");
$result = array_intersect($array1, $array2);
print_r($result);

>> green
   red

Java - Liste.retainAll

Collection listOne = new ArrayList(Arrays.asList("milan","dingo", "elpha", "hafil", "meat", "iga", "neeta.peeta"));
Collection listTwo = new ArrayList(Arrays.asList("hafil", "iga", "binga", "mike", "dingo"));

listOne.retainAll( listTwo );
System.out.println( listOne );

>> dingo, hafil, iga

1 votes

Python peut également le faire avec fixe . J'imagine que de nombreux langages ont également un type d'ensemble qui peut gérer cela.

0 votes

Je peux parier retainAll d'une Arraylist (en fait, toutes les implémentations de List dans std java) font un O(n^2).

12voto

Moataz Elmasry Points 1394

Puisque cela me semble être un algorithme de chaîne de caractères, je suppose pour le moment qu'il n'est pas possible de trier cette séquence (donc chaîne de caractères). Algorithme de la plus longue séquence commune (LCS)

En supposant que la taille des entrées est constante, le problème est d'une complexité de O(nxm), (longueur des deux entrées).

1 votes

Mais pourquoi un O(n*m) solution compliquée lorsqu'il y a O(n+m) y O(nlog(m)) ceux ? :|

0 votes

@amit la solution de programmation dynamique prend O(n*m), laquelle prend O(n+m) ? pour la solution O(nlog(m)) je suppose que vous parlez de tri, non ? ce qui est quelque chose que j'ai choisi d'ignorer

1 votes

Comment LCS va-t-il donner l'intersection de 2 chaînes de caractères ? Nous pourrions manquer de nombreux caractères. par exemple, s1=[A B D C E F] et s2=[C D E F G H] ici, LCS sera [D E F] alors que l'intersection des 2 chaînes est [C D E F] ! Est-ce que quelque chose m'échappe ici ?

5voto

Mik378 Points 9437
    public static void main(String[] args) {
        char[] a = {'a', 'b', 'c', 'd'};
        char[] b = {'c', 'd', 'e', 'f'};
        System.out.println(intersect(a, b));
    }

    private static Set<Character> intersect(char[] a, char[] b) {
        Set<Character> aSet = new HashSet<Character>();
        Set<Character> intersection = new HashSet<Character>();
        for (char c : a) {
            aSet.add(c);
        }
        for (char c : b) {
            if (aSet.contains(c)) {
                intersection.add(c);
            }
        }
        return intersection;
    }

0 votes

Les performances seront légèrement inférieures à l'optimum, puisque vous n'avez pas besoin de créer le deuxième ensemble juste pour vérifier s'il contient des éléments du deuxième ensemble.

0 votes

Je vérifierais également la taille, si b est énorme et a petit, votre code s'exécutera plus lentement en vérifiant le plus grand ensemble.

0 votes

@user1281385 Mais dans ce cas, traiter avec des caractères, contains() est toujours d'une complexité O(1) quelle que soit la taille de l'ensemble.

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