Algorithmes de collections Java

Les algorithmes de collections Java font référence àjava.util.Collectionsun ensemble de méthodes statiques fournies dans la classe Collections, utilisées pour effectuer des opérations courantes telles que le tri, la recherche, le mélange, le remplissage, etc. sur des collections (List, Set, Map, etc.).

Ces algorithmes sont intégrés au JDK et sont hautement optimisés. Les développeurs n'ont pas besoin de les écrire à partir de zéro ; ils peuvent simplement les appeler directement.


Aperçu des algorithmes de collections

java.util.CollectionsCollections est une classe utilitaire ; toutes ses méthodes sont statiques, similaire à la classe utilitaire Arrays pour les tableaux.

Le tableau suivant répertorie les algorithmes de collection les plus couramment utilisés et leurs objectifs :

Catégorie d'algorithmeNom de la méthodeDescription
Trisort()Trie une List par ordre croissant
Mélangeshuffle()Mélange de manière aléatoire l'ordre des éléments d'une List
Inversionreverse()Inverse l'ordre des éléments dans une List
Recherche binairebinarySearch()Effectue une recherche binaire pour un élément spécifié dans une List triée
Remplissagefill()Remplace tous les éléments d'une List par un élément spécifié
Copycopy()Copie les éléments d'une List vers une autre List
Maximum/Minimummax() / min()Renvoie l'élément maximum ou minimum dans une collection
Comptage des fréquencesfrequency()Renvoie le nombre d'occurrences d'un élément spécifié dans une collection
Vérification de disjonctiondisjoint()Détermine si deux collections ont des éléments communs
Collection immuableunmodifiableXxx()Renvoie une vue en lecture seule d'une collection
Sécurité des threadssynchronizedXxx()Renvoie un wrapper thread-safe pour une collection

Tri

Collections.sort()C'est l'algorithme de collection le plus couramment utilisé, qui trie les éléments d'une List par ordre croissant.

Sous le capot, il utilise un tri fusion optimisé (TimSort), avec une complexité temporelle en O(n log n) et des performances stables.

Le processus de tri est illustré dans la figure suivante :

Input Original list 5 2 8 1 9 Arrow sort() Output Sorted list 1 2 5 8 9 Custom Sorting Custom sorting methods Natural order Comparable interface Custom comparator Comparator interface Reverse sorting Comparator.reverseOrder()

Tri par ordre naturel

Lorsque la classe des éléments implémenteComparablel'interface, vous pouvez appeler directement sort() pour trier par ordre naturel.

Exemple

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class SortExample {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("example");
        names.add("EXAMPLE");
        names.add("apple");
        names.add("banana");

        System.out.println("Before sorting: " + names);

        // Trier par ordre naturel (par point de code Unicode)
        Collections.sort(names);

        System.out.println("Après tri : " + names);
    }
}

Résultat de sortie :

排序前: [example, EXAMPLE, apple, banana]
排序后: [EXAMPLE, apple, banana, example]

Tri personnalisé avec Comparator

En utilisantComparator, vous pouvez trier selon n'importe quelle règle personnalisée sans modifier la classe de l'élément elle-même.

Exemple : Trier par longueur de String

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class CustomSortExample {
    public static void main(String[] args) {
        List<String> words = new ArrayList<>();
        words.add("example");
        words.add("java");
        words.add("algorithm");
        words.add("C");

        System.out.println("Avant le tri : " + words);

        // Trier par longueur de chaîne dans l'ordre croissant
        Collections.sort(words, new Comparator<String>() {
            @Override
            public int compare(String s1, String s2) {
                return s1.length() - s2.length();
            }
        });

        System.out.println("Après tri par longueur : " + words);
    }
}

Résultat de sortie :

排序前: [example, java, algorithm, C]
按长度排序后: [C, java, example, algorithm]

Java 8+ peut utiliser des expressions Lambda pour simplifier le code :

Exemple : syntaxe Lambda

// Trier par longueur en utilisant une expression Lambda
Collections.sort(words, (s1, s2) -> s1.length() - s2.longueur());

// Ou utiliser Comparator.comparingInt
Collections.sort(words, Comparator.comparingInt(String::longueur));

// Tri inversé
Collections.sort(words, Comparator.reverseOrder());

// Trier par longueur en ordre inverse
Collections.sort(words, Comparator.comparingInt(String::length).reversed());

Mélange

Collections.shuffle()Utilise l'algorithme de Fisher-Yates pour mélanger aléatoirement l'ordre des éléments d'une List.

Original list A B C D shuffle() After shuffling (random) C D A B ? ? ? Note The result is different each time it runs Fixed seed shuffle(list, new Random(42)) accepts a fixed seed, making the shuffling result reproducible.

Exemple

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ShuffleExample {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        for (int i = 1; i <= 10; i++) {
            numbers.add(i);
        }

        System.out.println("Liste originale : " + numbers);

        // Mélanger aléatoirement l'ordre
        Collections.shuffle(numbers);
        System.out.println("Premier mélange : " + numbers);

        Collections.shuffle(numbers);
        System.out.println("Deuxième mélange : " + numbers);
    }
}

Résultat de sortie :

原始列表: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
第一次混排: [7, 3, 9, 1, 5, 10, 8, 2, 4, 6]
第二次混排: [4, 10, 1, 8, 6, 3, 2, 9, 5, 7]

Recherche binaire

Collections.binarySearch()Utilise l'algorithme de recherche binaire pour localiser un élément cible dans une List triée.

La complexité temporelle est O(log n), bien meilleure que le O(n) d'un parcours linéaire.

Prérequis important : la List doit déjà être triée par ordre croissant ; sinon, le résultat est indéfini.

Exemple

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class BinarySearchExample {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(10);
        numbers.add(30);
        numbers.add(20);
        numbers.add(50);
        numbers.add(40);

        // Il faut d'abord trier
        Collections.sort(numbers);
        System.out.println("Liste triée : " + numbers);

        // Recherche binaire : la valeur de retour est l'index (>= 0 signifie trouvé)
        int index = Collections.binarySearch(numbers, 30);
        System.out.println("Position de l'index de 30 : " + index);

        // Recherche d'un élément inexistant : renvoie une valeur négative = -(point d'insertion) - 1
        int notFound = Collections.binarySearch(numbers, 25);
        System.out.println("Valeur de retour pour 25 (introuvable) : " + notFound);
        // Point d'insertion = -(notFound + 1) = 2, il devrait donc être inséré à l'index 2
    }
}

Résultat de sortie :

排序后列表: [10, 20, 30, 40, 50]
30 的索引位置: 2
25 的返回值(不存在): -3

Lorsque binarySearch() ne trouve pas un élément, elle renvoie une valeur négative, la formule est-(point d'insertion) - 1Le point d'insertion est l'index où l'élément serait situé s'il existait. L'astuce de cette conception est que si l'élément existe, un index non négatif est renvoyé ; s'il n'existe pas, une valeur négative est inévitablement renvoyée, donc il n'y a pas d'ambiguïté.


Maximum et minimum

Collections.max()etCollections.min()Renvoie l'élément maximum/minimum d'une collection selon l'ordre naturel ou un comparateur spécifié.

Exemple

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class MaxMinExample {
    public static void main(String[] args) {
        List<String> words = new ArrayList<>();
        words.add("example");
        words.add("algorithm");
        words.add("java");
        words.add("code");

        // Obtenir le maximum et le minimum par ordre naturel (ordre lexicographique)
        String maxWord = Collections.max(words);
        String minWord = Collections.min(words);
        System.out.println("Maximum lexicographique : " + maxWord);
        System.out.println("Minimum lexicographique : " + minWord);

        // Obtenir le maximum et le minimum par longueur de chaîne
        String longest = Collections.max(words,
            Comparator.comparingInt(String::length));
        String shortest = Collections.min(words,
            Comparator.comparingInt(String::length));
        System.out.println("Longueur maximale: " + longest);
        System.out.println("Longueur minimale: " + shortest);
    }
}

Résultat de sortie :

字典序最大: example
字典序最小: algorithm
长度最长: algorithm
长度最短: code

Comptage de fréquence et vérification de disjonction

Collections.frequency()Compte le nombre d'occurrences d'un élément spécifié dans une collection.

Collections.disjoint()Détermine si deux collections n'ont aucun élément en commun du tout.

Exemple

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class FrequencyDisjointExample {
    public static void main(String[] args) {
        List<String> items = new ArrayList<>();
        items.add("example");
        items.add("java");
        items.add("example");
        items.add("python");
        items.add("example");

        // Compter les occurrences de "example"
        int count = Collections.frequency(items, "example");
        System.out.println("\"example\""Occurrences: " + count);

        // Vérifier si les deux collections sont disjointes
        List<String> groupA = new ArrayList<>();
        groupA.add("java");
        groupA.add("python");

        List<String> groupB = new ArrayList<>();
        groupB.add("C++");
        groupB.add("go");

        boolean noCommon = Collections.disjoint(groupA, groupB);
        System.out.println("groupA et groupB sont : " + noCommon);
    }
}

Résultat de sortie :

"example" 出现次数: 3
groupA 与 groupB 不相交: true

Collections immuables et wrappers thread-safe

Collections fournit des méthodes pour envelopper des collections ordinaires en collections immuables ou en collections thread-safe.

Collections immuables (non modifiables)

En appelantCollections.unmodifiableList()et d'autres méthodes, on peut créer une vue en lecture seule.

Toute opération de modification lancera UnsupportedOperationException.

Collections thread-safe (synchronisées)

En appelantCollections.synchronizedList()et d'autres méthodes, on peut créer un wrapper thread-safe.

Dans un environnement multithread, les appels individuels aux méthodes sur ces collections sont sûrs, mais les opérations composées nécessitent encore une synchronisation externe.

Exemple

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class WrapperExample {
    public static void main(String[] args) {
        List<String> original = new ArrayList<>();
        original.add("example");
        original.add("java");

        // Créer une vue immuable
        List<String> readOnly = Collections.unmodifiableList(original);
        System.out.println("Vue immuable : " + readOnly);

        // readOnly.add("error"); // L'exécution de ceci lèvera une exception

        // Après modification de la liste d'origine, la vue immuable reflète également les modifications
        original.add("python");
        System.out.println("Après modification de l'original : " + readOnly);

        // Créer un wrapper thread-safe
        List<String> syncList = Collections.synchronizedList(
            new ArrayList<>());
        syncList.add("thread-safe");

        // Une synchronisation manuelle est nécessaire lors de l'itération
        synchronized (syncList) {
            for (String item : syncList) {
                System.out.println(item);
            }
        }
    }
}

Résultat de sortie :

不可变视图: [example, java]
原始修改后: [example, java, python]
thread-safe

Une vue immuable empêche uniquement de modifier la collection via cette vue ; la collection sous-jacente reste mutable. Si vous avez besoin d'une collection vraiment immuable, utilisezList.of(),、Set.of() de Java 9+.et d'autres méthodes.


Opérations algorithmiques courantes en un coup d'œil

Ce qui suit résume les méthodes d'appel et les complexités temporelles des algorithmes courants dans Collections :

OpérationAppel de méthodeComplexité temporellePrérequis
TriCollections.sort(list)O(n log n)Les éléments implémentent Comparable
Tri personnaliséCollections.sort(list, comp)O(n log n)Fournir un Comparator
InversionCollections.reverse(list)O(n)Aucune
MélangeCollections.shuffle(list)O(n)Aucune
Recherche binaireCollections.binarySearch(list, key)O(log n)La liste est triée
RemplissageCollections.fill(list, obj)O(n)Aucune
CopieCollections.copy(dest, src)O(n)dest.size() >= src.size()
MaximumCollections.max(coll)O(n)Les éléments implémentent Comparable
MinimumCollections.min(coll)O(n)Les éléments implémentent Comparable
Comptage de fréquenceCollections.frequency(coll, obj)O(n)Aucune
Échange d'élémentsCollections.swap(list, i, j)O(1)Les indices sont valides
RotationCollections.rotate(list, distance)O(n)Aucune

Notes

binarySearch() doit être appelée sur une liste triée ; le résultat renvoyé pour une liste non triée n'est pas défini. Si la liste contient des éléments dupliqués, il n'y a aucune garantie quant à celui qui sera trouvé.

Le wrapper renvoyé par synchronizedXxx() ne garantit l'atomicité qu'au niveau d'un seul appel de méthode. Les opérations composées (telles que if(!list.contains(x)) list.add(x)) nécessitent toujours un bloc synchronized externe.

Différence entre sort() et List.sort() :

Depuis Java 8, l'interface List a ajouté la méthode par défaut sort(Comparator).

list.sort(null) trie selon l'ordre naturel, équivalent à Collections.sort(list).

Les deux ont la même implémentation sous-jacente, mais list.sort() est plus concis syntaxiquement.

Suggestions de comparaison des performances :

Pour de petits volumes de données (moins de 100 éléments), les différences de performance entre les différents algorithmes sont négligeables.

Pour de grands volumes de données, binarySearch() est nettement supérieur à la recherche linéaire de indexOf().

Lorsque des recherches fréquentes sont nécessaires, envisagez d'utiliser HashSet au lieu de List, car la complexité de recherche est O(1).

Autres extensions