Algoritmos de colecciones de Java

Los algoritmos de colecciones de Java se refieren ajava.util.Collectionsun conjunto de métodos estáticos proporcionados por la clase, utilizados para realizar operaciones comunes como ordenamiento, búsqueda, barajado, relleno, etc., sobre colecciones (List, Set, Map, etc.).

Estos algoritmos están implementados de forma incorporada en el JDK y altamente optimizados; los desarrolladores no necesitan escribirlos desde cero, solo llamarlos directamente.


Resumen de algoritmos de Collections

java.util.Collectionses una clase de utilidad, todos los métodos son estáticos, similar a la clase de utilidad Arrays para operar con arreglos.

La tabla siguiente enumera los algoritmos de colecciones más comunes y sus usos:

Categoría de algoritmoNombre del métodoDescripción de la función
Ordenamientosort()Ordena una List en orden ascendente
Barajadoshuffle()Mezcla aleatoriamente el orden de los elementos en una List
Inversiónreverse()Invierte el orden de los elementos en una List
Búsqueda binariabinarySearch()Busca binariamente un elemento especificado en una List ordenada
Rellenofill()Reemplaza todos los elementos de una List con un elemento especificado
Copiacopy()Copia los elementos de una List a otra List
Máximo/Mínimomax() / min()Devuelve el elemento máximo o mínimo de una colección
Conteo de frecuenciafrequency()Devuelve el número de veces que aparece un elemento especificado en la colección
Verificación de disyuncióndisjoint()Determina si dos colecciones tienen elementos en común
Colección inmutableunmodifiableXxx()Devuelve una vista de solo lectura de la colección
Seguridad para hilossynchronizedXxx()Devuelve un envoltorio de colección seguro para hilos

Ordenamiento (Sorting)

Collections.sort()Es el algoritmo de colecciones más utilizado; ordena los elementos de una List en orden ascendente.

En su interior utiliza una variante optimizada de ordenamiento por mezcla (TimSort), con complejidad temporal O(n log n) y rendimiento estable.

El proceso de ordenamiento se muestra en la siguiente figura:

Entrada Lista original 5 2 8 1 9 Flecha sort() Salida Lista ordenada 1 2 5 8 9 Ordenamiento personalizado Forma de ordenamiento personalizado Orden natural Interfaz Comparable Comparador personalizado Interfaz Comparator Ordenamiento inverso Comparator.reverseOrder()

Ordenamiento por orden natural

La clase de elementos implementaComparablela interfaz, y luego se puede llamar directamente a sort() para ordenar según el orden natural.

Ejemplo

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("ejemplo");
        names.add("EJEMPLO");
        names.add("apple");
        names.add("banana");

        System.out.println(Antes de ordenar: + names);

        // Ordenamiento por orden natural (según puntos de código Unicode)
        Collections.sort(names);

        System.out.println(Después de ordenar: + names);
    }
}

Resultado de salida:

排序前: [ejemplo, EJEMPLO, apple, banana]
排序后: [EJEMPLO, apple, banana, ejemplo]

Ordenamiento con Comparator personalizado

UsandoComparatorse puede ordenar según cualquier regla personalizada sin necesidad de modificar la propia clase de elementos.

Ejemplo: ordenar por longitud de cadena

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("ejemplo");
        words.add("java");
        words.add("algorithm");
        words.add("C");

        System.out.println(Antes de ordenar: + words);

        // Ordenar por longitud de cadena en orden ascendente
        Collections.sort(words, new Comparator<String>() {
            @Override
            public int compare(String s1, String s2) {
                return s1.length() - s2.length();
            }
        });

        System.out.println(Después de ordenar por longitud: + words);
    }
}

Resultado de salida:

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

Java 8+ puede usar expresiones Lambda para simplificar la escritura:

Ejemplo: sintaxis Lambda

// Expresión Lambda para ordenar por longitud
Collections.sort(words, (s1, s2) -> s1.length() - s2.length());

// O usar Comparator.comparingInt
Collections.sort(words, Comparator.comparingInt(String::length));

// Ordenamiento inverso
Collections.sort(words, Comparator.reverseOrder());

// Ordenar por longitud en orden inverso
Collections.sort(words, Comparator.comparingInt(String::length).reversed());

Barajado (Shuffling)

Collections.shuffle()Utiliza el algoritmo de Fisher-Yates para mezclar aleatoriamente el orden de los elementos en una List.

Lista original A B C D shuffle() Después del barajado (aleatorio) C D A B ? ? ? Explicación El resultado es diferente en cada ejecución Semilla fija shuffle(list, new Random(42)) permite pasar una semilla fija para que el resultado del barajado sea reproducible

Ejemplo

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(Lista original: + numbers);

        // Mezclar aleatoriamente el orden
        Collections.shuffle(numbers);
        System.out.println(Primer barajado: + numbers);

        Collections.shuffle(numbers);
        System.out.println(Segundo barajado: + numbers);
    }
}

Resultado de salida:

原始列表: [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]

Búsqueda binaria (Binary Search)

Collections.binarySearch()Utiliza el algoritmo de búsqueda binaria para localizar un elemento objetivo en una List ordenada.

La complejidad temporal es O(log n), muy superior a la O(n) del recorrido lineal.

Requisito importante: la List debe estar ordenada en orden ascendente; de lo contrario, el resultado no es determinista.

Ejemplo

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);

        // Primero debe estar ordenada
        Collections.sort(numbers);
        System.out.println(Lista después de ordenar: + numbers);

        // Búsqueda binaria: el valor devuelto es el índice (>= 0 indica que se encontró)
        int index = Collections.binarySearch(numbers, 30);
        System.out.println(Posición del índice de 30: + index);

        // Buscar un elemento inexistente: devuelve un valor negativo = -(punto de inserción) - 1
        int notFound = Collections.binarySearch(numbers, 25);
        System.out.println(Valor devuelto para 25 (inexistente): + notFound);
        // Punto de inserción = -(notFound + 1) = 2, debería insertarse en el índice 2
    }
}

Resultado de salida:

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

Cuando binarySearch() no encuentra el elemento, devuelve un valor negativo; la fórmula es-(punto de inserción) - 1El punto de inserción es el índice en el que debería ubicarse el elemento si existiera. Lo ingenioso de este diseño es que si el elemento existe se devuelve un índice no negativo, y si no existe se devuelve inevitablemente un valor negativo, sin ambigüedad.


Valores máximo y mínimo

Collections.max()yCollections.min()Devuelve el elemento máximo/mínimo de una colección según el orden natural o un comparador especificado.

Ejemplo

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("ejemplo");
        words.add("algorithm");
        words.add("java");
        words.add("code");

        // Tomar máximo y mínimo según el orden natural (lexicográfico)
        String maxWord = Collections.max(words);
        String minWord = Collections.min(words);
        System.out.println(Máximo lexicográfico: + maxWord);
        System.out.println(Mínimo lexicográfico: + minWord);

        // Tomar máximo y mínimo por longitud de cadena
        String longest = Collections.max(words,
            Comparator.comparingInt(String::length));
        String shortest = Collections.min(words,
            Comparator.comparingInt(String::length));
        System.out.println(De mayor longitud: + longest);
        System.out.println(De menor longitud: + shortest);
    }
}

Resultado de salida:

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

Conteo de frecuencia y verificación de disyunción

Collections.frequency()Cuenta el número de veces que aparece un elemento especificado en la colección.

Collections.disjoint()Determina si dos colecciones no tienen ningún elemento en común.

Ejemplo

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("ejemplo");
        items.add("java");
        items.add("ejemplo");
        items.add("python");
        items.add("ejemplo");

        // Contar cuántas veces aparece "ejemplo"
        int count = Collections.frequency(items, "ejemplo");
        System.out.println("\"ejemplo\"Número de apariciones: + count);

        // Determinar si dos colecciones son disjuntas
        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 y groupB son disjuntas: + noCommon);
    }
}

Resultado de salida:

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

Colecciones inmutables y envoltorios seguros para hilos

Collections proporciona métodos para envolver colecciones ordinarias como colecciones inmutables o colecciones seguras para hilos.

Colecciones inmutables (Unmodifiable)

Al llamarCollections.unmodifiableList()a métodos como estos, se puede crear una vista de solo lectura.

Cualquier operación de modificación lanzará una UnsupportedOperationException.

Colecciones seguras para hilos (Synchronized)

Llamando aCollections.synchronizedList()métodos como estos, se puede crear un envoltorio seguro para hilos.

En un entorno multihilo, las llamadas a métodos individuales de estas colecciones son seguras, pero las operaciones compuestas aún requieren sincronización externa.

Ejemplo

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("ejemplo");
        original.add("java");

        // Crear una vista inmutable
        List<String> readOnly = Collections.unmodifiableList(original);
        System.out.println(Vista inmutable: + readOnly);

        // readOnly.add("error"); // La ejecución lanzará una excepción

        // Después de modificar la lista original, la vista inmutable también reflejará los cambios
        original.add("python");
        System.out.println(Después de modificar la original: + readOnly);

        // Crear un envoltorio seguro para hilos
        List<String> syncList = Collections.synchronizedList(
            new ArrayList<>());
        syncList.add("thread-safe");

        // Al iterar, se necesita sincronización manual
        synchronized (syncList) {
            for (String item : syncList) {
                System.out.println(item);
            }
        }
    }
}

Resultado de salida:

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

La vista inmutable solo impide modificar la colección a través de esa vista; la colección subyacente sigue siendo mutable. Si se necesita una colección verdaderamente inmutable, use los métodos de Java 9+List.of()、Set.of()como estos.


Resumen de operaciones de algoritmos comunes

A continuación se resumen los métodos de llamada y la complejidad temporal de los algoritmos comunes en Collections:

OperaciónLlamada al métodoComplejidad temporalRequisitos previos
OrdenaciónCollections.sort(list)O(n log n)Los elementos implementan Comparable
Ordenación personalizadaCollections.sort(list, comp)O(n log n)Proporcionar un Comparator
InversiónCollections.reverse(list)O(n)Ninguno
BarajadoCollections.shuffle(list)O(n)Ninguno
Búsqueda binariaCollections.binarySearch(list, key)O(log n)La lista está ordenada
RellenoCollections.fill(list, obj)O(n)Ninguno
CopiaCollections.copy(dest, src)O(n)dest.size() >= src.size()
MáximoCollections.max(coll)O(n)Los elementos implementan Comparable
MínimoCollections.min(coll)O(n)Los elementos implementan Comparable
FrecuenciaCollections.frequency(coll, obj)O(n)Ninguno
Intercambio de elementosCollections.swap(list, i, j)O(1)Índice válido
RotaciónCollections.rotate(list, distance)O(n)Ninguno

Notas

binarySearch() debe llamarse sobre una lista ya ordenada; el resultado devuelto para una lista no ordenada no está definido. Si la lista contiene elementos duplicados, no se garantiza cuál de ellos se encontrará.

Los envoltorios devueltos por synchronizedXxx() solo garantizan atomicidad a nivel de una única llamada al método. Las operaciones compuestas (como if(!list.contains(x)) list.add(x)) aún requieren un bloque synchronized externo.

Diferencias entre sort() y List.sort():

Desde Java 8, la interfaz List añadió el método predeterminado sort(Comparator).

list.sort(null) ordena usando el orden natural, equivalente a Collections.sort(list).

Ambos tienen la misma implementación subyacente, pero list.sort() es más conciso de escribir.

Recomendaciones de comparación de rendimiento:

Para cantidades pequeñas de datos (< 100 elementos), las diferencias de rendimiento entre los algoritmos son despreciables.

Para cantidades grandes de datos, binarySearch() es muy superior a la búsqueda lineal de indexOf().

Si se necesitan búsquedas frecuentes, considere usar HashSet en lugar de List; la complejidad de consulta es O(1).

Otras extensiones