SortedList <>, SortedDictionary <> y Diccionario <>

Respuestas:

101
  1. Al iterar sobre los elementos en cualquiera de los dos, los elementos se ordenarán. No es así con Dictionary<T,V>.

  2. MSDN aborda la diferencia entre SortedList<T,V>y SortedDictionary<T,V>:

La clase genérica SortedDictionary (TKey, TValue) es un árbol de búsqueda binario con recuperación O (log n), donde n es el número de elementos en el diccionario. En este sentido, es similar a la clase genérica SortedList (TKey, TValue). Las dos clases tienen modelos de objetos similares y ambas tienen recuperación O (log n). Donde las dos clases difieren es en el uso de la memoria y la velocidad de inserción y eliminación:

SortedList (TKey, TValue) usa menos memoria que SortedDictionary (TKey, TValue).

SortedDictionary (TKey, TValue) tiene operaciones de inserción y eliminación más rápidas para datos no clasificados: O (log n) en contraposición a O (n) para SortedList (TKey, TValue).

Si la lista se completa de una vez a partir de datos ordenados, SortedList (TKey, TValue) es más rápido que SortedDictionary (TKey, TValue).

Szymon Rozga
fuente
21
Otra diferencia práctica, es que en SortedListusted puede recuperar por índice (en contraposición a la recuperación por clave) y en SortedDictionaryusted no puede.
Andrew Savinykh
65

ingrese la descripción de la imagen aquí

Mencionaría la diferencia entre diccionarios.

La imagen de arriba muestra que Dictionary<K,V>es igual o más rápido en todos los casos que el Sortedanalógico, pero si se requiere el orden de los elementos, por ejemplo, para imprimirlos, Sortedse elige uno.

Src: http://people.cs.aau.dk/~normark/oop-csharp/html/notes/collections-note-time-complexity-dictionaries.html

Lev
fuente
1
Excelente vista general. Si bien no está en la pregunta original, debe tenerse en cuenta que si elige entre las Immutableversiones de estos diccionarios, las Sortedversiones a menudo son en realidad un 40-50% más rápidas que las contrapartes no clasificadas (aún O(log(n)), pero notablemente más rápidas por operación) . Sin embargo, los tiempos pueden diferir dependiendo de qué tan ordenada esté la entrada. Ver stackoverflow.com/a/30638592/111575
Abel
21

Para resumir los resultados de una prueba de rendimiento: SortedList vs SortedDictionary vs Dictionary vs Hashtable , los resultados de mejor a peor para diferentes escenarios:

Uso de memoria:

SortedList<T,T>
Hashtable
SortedDictionary<T,T>
Dictionary<T,T>

Inserciones:

Dictionary<T,T>
Hashtable
SortedDictionary<T,T>
SortedList<T,T>

Operaciones de búsqueda:

Hashtable
Dictionary<T,T>
SortedList<T,T>
SortedDictionary<T,T>

operaciones de bucle foreach

SortedList<T,T>
Dictionary<T,T>
Hashtable
SortedDictionary<T,T>
NullReference
fuente
1
Al examinar los resultados de estas pruebas, uno puede cuestionar la razón de ser de SortedDictionary.
beawolf
1
Si lo Collectionnecesita sorted, puede olvidarse de Hashtabley Dictionary: si llena su Colección de una sola vez -> vaya a SortedList, pero si anticipa que a menudo necesitará .Addy .Removeelementos -> vaya a SortedDictionary.
Ama
Tal vez sea necesario aclarar lo que sortedsignifica: cuando hace un en For Each MyItem in Collectionlugar de ser procesado en el orden en que editó originalmente .Addlos elementos, a sorted Collectionlos procesará en un orden de acuerdo con los criterios de los Keyvalores (definidos en un IComparer). Por ejemplo, si sus claves son cadenas, su colección se procesará de forma predeterminada según el orden alfabético de sus claves, pero siempre puede definir una regla de clasificación personalizada.
Ama
9
  1. Cuando desee que la colección se ordene por clave al iterar sobre ella. Si no necesita que se ordenen sus datos, estará mejor con solo un diccionario, tendrá un mejor rendimiento.

  2. SortedList y SortedDictionary hacen prácticamente lo mismo, pero se implementan de manera diferente, por lo que tienen diferentes fortalezas y debilidades que se explican aquí .

Caballero meta
fuente
8

Puedo ver que las respuestas propuestas se centran en el rendimiento. El artículo que se proporciona a continuación no proporciona nada nuevo con respecto al rendimiento, pero explica los mecanismos subyacentes. También tenga en cuenta que no se centra en los tres Collectiontipos mencionados en la pregunta, sino que aborda todos los tipos del System.Collections.Genericespacio de nombres.

http://geekswithblogs.net/BlackRabbitCoder/archive/2011/06/16/c.net-fundamentals-choosing-the-right-collection-class.aspx

Extractos:

Diccionario <>

El Diccionario es probablemente la clase contenedora asociativa más utilizada. El Diccionario es la clase más rápida para búsquedas / inserciones / eliminaciones asociativas porque usa una tabla hash debajo de las cubiertas . Debido a que las claves tienen hash, el tipo de clave debe implementar correctamente GetHashCode () y Equals () de manera apropiada o debe proporcionar un IEqualityComparer externo al diccionario en construcción. El tiempo de inserción / eliminación / búsqueda de elementos en el diccionario se amortiza como tiempo constante - O (1) - lo que significa que no importa cuán grande sea el diccionario, el tiempo que se tarda en encontrar algo permanece relativamente constante. Esto es muy deseable para búsquedas de alta velocidad. El único inconveniente es que el diccionario, por la naturaleza del uso de una tabla hash, no está ordenado, por lo queno puede recorrer fácilmente los elementos de un diccionario en orden .

SortedDictionary <>

El SortedDictionary es similar al Diccionario en uso pero muy diferente en implementación. El SortedDictionary usa un árbol binario debajo de las cubiertas para mantener los elementos en orden por clave . Como consecuencia de la ordenación, el tipo utilizado para la clave debe implementar IComparable correctamente para que las claves se puedan ordenar correctamente. El diccionario ordenado intercambia un poco de tiempo de búsqueda por la capacidad de mantener los elementos en orden, por lo tanto, los tiempos de inserción / eliminación / búsqueda en un diccionario ordenado son logarítmicos - O (log n). Generalmente hablando, con tiempo logarítmico, puedes duplicar el tamaño de la colección y solo tiene que realizar una comparación extra para encontrar el artículo. Use el SortedDictionary cuando desee búsquedas rápidas pero también desee poder mantener la colección en orden por clave.

SortedList <>

SortedList es la otra clase de contenedor asociativo ordenada en los contenedores genéricos. Una vez más, SortedList, como SortedDictionary, usa una clave para ordenar los pares clave-valor . Sin embargo, a diferencia de SortedDictionary, los elementos de una SortedList se almacenan como una matriz ordenada de elementos. Esto significa que las inserciones y eliminaciones son lineales - O (n) - porque eliminar o agregar un elemento puede implicar mover todos los elementos hacia arriba o hacia abajo en la lista. Sin embargo, el tiempo de búsqueda es O (log n) porque SortedList puede usar una búsqueda binaria para encontrar cualquier elemento en la lista por su clave. Entonces, ¿por qué querrías hacer esto? Bueno, la respuesta es que si va a cargar SortedList por adelantado, las inserciones serán más lentas, pero debido a que la indexación de matrices es más rápida que los siguientes enlaces de objetos, las búsquedas son marginalmente más rápidas que un SortedDictionary. Una vez más, usaría esto en situaciones en las que desea búsquedas rápidas y desea mantener la colección en orden por clave, y donde las inserciones y eliminaciones son raras.


Resumen provisional de los procedimientos subyacentes

Los comentarios son bienvenidos, ya que estoy seguro de que no hice todo bien.

  • Todas las matrices son de tamaño n.
  • Matriz no ordenada = .Add / .Remove es O (1), pero .Item (i) es O (n).
  • Array ordenado = .Add / .Remove es O (n), pero .Item (i) es O (log n).

Diccionario

Memoria

KeyArray(n) -> non-sorted array<pointer>
ItemArray(n) -> non-sorted array<pointer>
HashArray(n) -> sorted array<hashvalue>

Añadir

  1. Agregar HashArray(n) = Key.GetHash# O (1)
  2. Agregar KeyArray(n) = PointerToKey# O (1)
  3. Agregar ItemArray(n) = PointerToItem# O (1)

Eliminar

  1. For i = 0 to n, encuentra idonde HashArray(i) = Key.GetHash # O (log n) (matriz ordenada)
  2. Eliminar HashArray(i)# O (n) (matriz ordenada)
  3. Quitar KeyArray(i)# O (1)
  4. Quitar ItemArray(i)# O (1)

Obtiene el objeto

  1. For i = 0 to n, encuentra idonde HashArray(i) = Key.GetHash# O (log n) (matriz ordenada)
  2. Regreso ItemArray(i)

Bucle a través

  1. For i = 0 to n, regreso ItemArray(i)

OrdenarDiccionario

Memoria

KeyArray(n) = non-sorted array<pointer>
ItemArray(n) = non-sorted array<pointer>
OrderArray(n) = sorted array<pointer>

Añadir

  1. Agregar KeyArray(n) = PointerToKey# O (1)
  2. Agregar ItemArray(n) = PointerToItem# O (1)
  3. For i = 0 to n, encuentra idónde KeyArray(i-1) < Key < KeyArray(i)(usando ICompare) # O (n)
  4. Agregar OrderArray(i) = n# O (n) (matriz ordenada)

Eliminar

  1. For i = 0 to n, encuentra idonde KeyArray(i).GetHash = Key.GetHash# O (n)
  2. Eliminar KeyArray(SortArray(i))# O (n)
  3. Eliminar ItemArray(SortArray(i))# O (n)
  4. Eliminar OrderArray(i)# O (n) (matriz ordenada)

Obtiene el objeto

  1. For i = 0 to n, encuentra idonde KeyArray(i).GetHash = Key.GetHash# O (n)
  2. Regreso ItemArray(i)

Bucle a través

  1. For i = 0 to n, regreso ItemArray(OrderArray(i))

SortedList

Memoria

KeyArray(n) = sorted array<pointer>
ItemArray(n) = sorted array<pointer>

Añadir

  1. For i = 0 to n, encuentra idónde KeyArray(i-1) < Key < KeyArray(i)(usando ICompare) # O (log n)
  2. Agregar KeyArray(i) = PointerToKey# O (n)
  3. Agregar ItemArray(i) = PointerToItem# O (n)

Eliminar

  1. For i = 0 to n, encuentra idonde KeyArray(i).GetHash = Key.GetHash# O (log n)
  2. Eliminar KeyArray(i)# O (n)
  3. Eliminar ItemArray(i)# O (n)

Obtiene el objeto

  1. For i = 0 to n, encuentra idonde KeyArray(i).GetHash = Key.GetHash# O (log n)
  2. Regreso ItemArray(i)

Bucle a través

  1. For i = 0 to n, regreso ItemArray(i)
Ama
fuente
0

Intentando asignar una puntuación de desempeño a cada caso presentado por @Lev, utilicé los siguientes valores:

  • O (1) = 3
  • O (log n) = 2
  • O (n) = 1
  • O (1) o O (n) = 2
  • O (log n) u O (n) = 1,5

Los resultados son (más alto = mejor):

Dictionary:       12.0 
SortedDictionary:  9.0 
SortedList:        6.5

Por supuesto, cada caso de uso dará más peso a determinadas operaciones.

Jaime
fuente
1
Como regla general, el peso de O (log n) sería log (n) / log (2) (+1 cada vez que n se duplica) mientras que el peso de O (n) sería n. Por lo tanto, su ponderación sería correcta para tamaños de hasta 4. Cualquier cosa más allá hará que su proporción 2: 1 aumente rápidamente. Por ejemplo, si n = 100, entonces debería tener O (log n) = 15. Siguiendo un pensamiento similar, su O (1) pesaría 100. Conclusión: O (n) pierde la batalla con bastante rapidez. Si no es así, significa que su arreglo es pequeño y la eficiencia no es un problema.
Ama