¿Es más rápido agregar a una colección y luego ordenarla o agregarla a una colección ordenada?

79

Si tengo una Mapcomo esta:

HashMap<Integer, ComparableObject> map;

y quiero obtener una colección de valores ordenados usando el orden natural, ¿qué método es el más rápido?

(UN)

Cree una instancia de una colección ordenable como ArrayList, agregue los valores y luego ordénela:

List<ComparableObject> sortedCollection = new ArrayList<ComparableObject>(map.values());
Collections.sort(sortedCollection);

(SEGUNDO)

Cree una instancia de una colección ordenada como TreeSet, luego agregue los valores:

Set<ComparableObject> sortedCollection = new TreeSet<ComparableObject>(map.values());

Tenga en cuenta que la colección resultante nunca se modifica, por lo que la clasificación solo debe realizarse una vez.

gutch
fuente
Depende del orden de los datos de entrada, por ejemplo. si obtiene muchas filas y usa ORDER BY, entonces es un caso, si tiene un conjunto aleatorio de guías, otro.
Boris Treukhov
¿Por qué no utilizar un TreeMap en su lugar?
Thorbjørn Ravn Andersen
TreeMap no ayudaría aquí porque la clasificación debe tener lugar en los valores ( ComparableObject) no en la clave ( Integer).
gutch
3
También tenga en cuenta que un conjunto solo admite entradas únicas. La colección de "valores" de un HashMap por otro lado puede contener duplicados. Desde ese ángulo, TreeSet no es una buena solución.
rompetroll
@gutch, puede encontrar mi respuesta en " stackoverflow.com/questions/3759112/… " para ser útil.
Richard

Respuestas:

87

TreeSet tiene una log(n)garantía de complejidad de tiempo para los add()/remove()/contains()métodos. Ordenar una ArrayListtoma de n*log(n)operaciones, pero add()/get()solo toma1 operación.

Entonces, si principalmente está recuperando y no clasifica con frecuencia, ArrayListes la mejor opción. Si ordena con frecuencia pero no recupera tanto TreeSet, sería una mejor opción.

fasseg
fuente
En mi caso, solo necesitamos iterar a través de la colección resultante, nunca se modifica. Entonces, según su respuesta, ArrayListes la mejor opción aquí.
gutch
Además, la clasificación de matrices se puede realizar en paralelo y tiene un rendimiento de caché mucho mejor.
Kaiser
21

En teoría, la clasificación al final debería ser más rápida. Mantener el estado ordenado a través del proceso podría implicar tiempo adicional de CPU.

Desde el punto de vista de CS, ambas operaciones son NlogN, pero 1 tipo debería tener una constante más baja.

BarrasMonstruo
fuente
4
+1 Uno de esos casos donde la teoría y la realidad se desconectan. :) En mi experiencia, la clasificación al final tiende a ser órdenes de magnitud más rápida ...
stevevls
A menos que sean O (N), que sería el caso de los datos enteros. Las colas de prioridad también involucran operaciones O (log N) para la inserción, eliminación y administración.
Richard
10

¿Por qué no utilizar lo mejor de ambos mundos? Si nunca lo volverá a usar, ordene usando un TreeSet e inicialice un ArrayList con el contenido

List<ComparableObject> sortedCollection = 
    new ArrayList<ComparableObject>( 
          new TreeSet<ComparableObject>(map.values()));

EDITAR:

He creado un punto de referencia (puede acceder a él en pastebin.com/5pyPMJav ) para probar los tres enfoques (ArrayList + Collections.sort, TreeSet y mi mejor enfoque de ambos mundos) y el mío siempre gana. El archivo de prueba crea un mapa con 10000 elementos, cuyos valores tienen un comparador intencionalmente terrible, y luego cada una de las tres estrategias tiene la oportunidad de a) ordenar los datos yb) iterar sobre ellos. Aquí hay una salida de muestra (puede probarla usted mismo):

EDITAR: He agregado un aspecto que registra llamadas a Thingy.compareTo (Thingy) y también he agregado una nueva estrategia basada en PriorityQueues que es mucho más rápida que cualquiera de las soluciones anteriores (al menos en la clasificación).

compareTo() calls:123490
Transformer ArrayListTransformer
    Creation: 255885873 ns (0.255885873 seconds) 
    Iteration: 2582591 ns (0.002582591 seconds) 
    Item count: 10000

compareTo() calls:121665
Transformer TreeSetTransformer
    Creation: 199893004 ns (0.199893004 seconds) 
    Iteration: 4848242 ns (0.004848242 seconds) 
    Item count: 10000

compareTo() calls:121665
Transformer BestOfBothWorldsTransformer
    Creation: 216952504 ns (0.216952504 seconds) 
    Iteration: 1604604 ns (0.001604604 seconds) 
    Item count: 10000

compareTo() calls:18819
Transformer PriorityQueueTransformer
    Creation: 35119198 ns (0.035119198 seconds) 
    Iteration: 2803639 ns (0.002803639 seconds) 
    Item count: 10000

Curiosamente, mi enfoque funciona mejor en iteración (habría pensado que no habría diferencias con el enfoque ArrayList en iteración, ¿tengo un error en mi punto de referencia?)

Descargo de responsabilidad: Sé que este es probablemente un punto de referencia terrible, pero te ayuda a hacerte entender y ciertamente no lo manipulé para que mi enfoque ganara.

(El código tiene una dependencia de apache commons / lang para los constructores equals / hashcode / compareTo, pero debería ser fácil refactorizarlo)

Sean Patrick Floyd
fuente
3
¿No sería eso realmente lo peor de ambos mundos? Todo lo que necesito es una colección en orden natural, que es lo que new TreeSet<ComparableObject>(map.values())regresa. Envolver eso en un ArrayListsolo va a agregar operaciones innecesarias.
gutch
1
El objetivo final fue un ordenado Collection... que TreeSetes. No veo ningún valor en convertir el conjunto en una lista aquí.
Gunslinger47
no está envolviendo, se está inicializando. y una lista de arrays es mejor para recuperar mientras que el conjunto de árboles es mejor para clasificar
Sean Patrick Floyd
4
¡Agradezco el esfuerzo que ha realizado para escribir el punto de referencia! Sin embargo, creo que tiene una falla. Parece que la JVM ejecuta Transformerinstancias que están más tarde en la lista más rápido que las anteriores: coloque BestOfBothWorldsTransformerprimero y de repente se ejecuta mucho más lento. Así que he reescrito su punto de referencia para seleccionar al azar un transformador y promediar los resultados. En mi prueba , TreeSetTransformerlate constantemente BestOfBothWorldsTransformer, que late constantemente ArrayListTransformer, ¡no es lo que esperaba en absoluto! Sin embargo, la diferencia es pequeña. Ver pastebin.com/L0t5QDV9
gutch
1
Sé cuál es su próxima pregunta: ¿qué pasa con PriorityQueueTransformer? ¿No es enormemente más rápido que los demás? Bueno, sí lo es, ¡lástima que no tenga el orden correcto! ¡Eche un vistazo a las listas generadas por cada transformador en mi código anterior y verá que PriorityQueueTransformer no está realmente en orden! ¿Quizás estoy usando PriorityQueueincorrectamente? ¿Tiene un ejemplo de cómo se ordena correctamente?
gutch
6

Asegúrese de leer mi comentario sobre TreeSet en la parte inferior si elige implementar B)

Si su aplicación solo hace clasificaciones ocasionales pero la recorre mucho, diría que es mejor que use una lista sencilla y sin clasificar. Ordénelo una vez y luego benefíciese de una iteración más rápida. La iteración es especialmente rápida en una lista de matrices.

Sin embargo, si desea que el orden de clasificación esté garantizado todo el tiempo o posiblemente esté agregando / eliminando elementos con frecuencia, use una colección ordenada y aproveche la iteración.

Entonces, en su caso, diría que A) es la mejor opción. La lista se ordena una vez, no cambia y, por lo tanto, se beneficia de ser una matriz. La iteración debería ser muy rápida, especialmente si sabe que es una ArrayList y puede usar directamente ArrayList.get () en lugar de un Iterador.

También agregaría que TreeSet por definición es un conjunto, lo que significa que los objetos son únicos. Un TreeSet determina la igualdad usando compareTo en su Comparator / Comparable. Es posible que te falten datos fácilmente si intentas agregar dos objetos cuyo compareTo devuelve un valor de 0. Por ejemplo, si agregas "C", "A", "B", "A" a un TreeSet, obtendrás "A", "B ", "C"

locka
fuente
1
Buen punto sobre TreeSetdatos potencialmente faltantes si compareTo devuelve 0. He determinado que en este caso particular la implementación compareTo nunca devolverá 0, por lo que ambos TreeSety ArrayListse comportarán igual. Sin embargo, ese problema me ha pillado antes, ¡así que gracias por el recordatorio!
gutch
Un PriorityQueue es probablemente mejor para ordenar una lista que un TreeSet.
locka
sí, en mi punto de referencia (ver mi respuesta) PriorityQueue supera a TreeSet en un 600 a 700%.
Sean Patrick Floyd
PriorityQueuede hecho funciona más rápido, pero cuando lo probé, los valores no estaban realmente ordenados, ¡obviamente por qué fue tan rápido! Tal vez malinterpreté cómo usar PriorityQueue ... un ejemplo de cómo funciona realmente sería útil.
gutch
Un PriorityQueue es solo una cola con un comparador / prueba comparable. Cuando agrega () elementos a la cola, la inserción compara el nuevo elemento con los que ya están allí para determinar la posición en la que insertar. Cuando sondea () la cola o la iteras, el contenido ya está ordenado. Espero que la inserción se realice a través de algún tipo de algoritmo recursivo, es decir, dividir la lista en dos y determinar en qué mitad insertarla, dividirla en dos nuevamente y así sucesivamente, por lo que el rendimiento será O (log N), que en teoría es el mismo que TreeSet / TreeMap, pero la implementación puede hacerlo más rápido.
locka
1

Collections.sort usa mergeSort que tiene O (nlog n).

TreeSettiene un árbol rojo-negro subyacente, las operaciones básicas tienen O (logn). Por tanto, n elementos también tiene O (nlog n).

Entonces ambos son el mismo algoritmo de Big O.

卢 声 远 Shengyuan Lu
fuente
6
Si bien esto parece cierto, cubre algunos costos importantes. MergeSort funciona en tiempo O (n log n), pero Red-Black requerirá O (n log n) para la inserción y nuevamente para la eliminación. La notación Big-O esconde importantes diferencias en los algoritmos.
Richard
0

Insertar en un SortedSet es O (log (n)) (¡PERO! La n actual y no la n final). Insertar en una lista es 1.

La ordenación en un SortedSet ya está incluida en la inserción, por lo que es 0. La ordenación en una Lista es O (n * log (n)).

Entonces, la complejidad total de SortedSet es O (n * k), k <log (n) para todos los casos excepto el último. En cambio, la complejidad total de la lista es O (n * log (n) + n), entonces O (n * log (n)).

Entonces, SortedSet matemáticamente tiene el mejor rendimiento. Pero al final, tiene un Set en lugar de una List (porque SortedList no existe) y Set le proporciona menos funciones que List. Entonces, en mi opinión, la mejor solución para las funciones y el rendimiento disponibles es la propuesta por Sean Patrick Floyd:

  • use un SortedSet para insertar,
  • ponga el SortedSet como parámetro para crear una lista para devolver.
George señores del castillo
fuente
0

Gran pregunta y grandes respuestas. Solo pensé en agregar algunos puntos a tener en cuenta:

  1. Si su colección a ordenar es de corta duración, por ejemplo, se usa como argumento para un método, y necesita la lista ordenada dentro del método, entonces use Collections.sort (colección). O si es un objeto de larga duración, pero es necesario clasificarlo muy raramente.

Justificación: la colección ordenada es necesaria para algo específico, y probablemente no agregará ni eliminará con mucha frecuencia. Por lo tanto, no le importan los elementos de la colección una vez que está ordenada. Básicamente:

ordenar -> usarlo -> olvidar

Si agrega un nuevo elemento a la colección ordenada, tendrá que ordenar la colección nuevamente, ya que el orden no está garantizado al insertar un nuevo elemento.

  1. Si la colección que se va a ordenar es de larga duración y / o si es un campo dentro de una clase y necesita que se ordene en todo momento, entonces debe usar una estructura de datos ordenada como TreeSet.

Justificación: Te preocupas por el orden de recogida en todo momento. Quieres que esté ordenado en todo momento. Entonces, si agrega o elimina elementos constantemente, tiene la garantía de que la colección está ordenada. Así que básicamente:

insertar / quitar -> usarlo (todo el tiempo tiene la garantía de que la colección está ordenada)

No hay un momento específico en el que necesite que se ordene la colección, sino que desea que la colección se ordene todo el tiempo.

La desventaja de usar TreeSet son los recursos que requiere para mantener la colección ordenada. Utiliza un árbol rojo-negro y requiere un costo de tiempo O (log n) para las operaciones get, put.

Mientras que si usa una colección simple, como ArrayList, las operaciones get, add son de tiempo constante O (1).

FraK
fuente