¿Por qué alguien usaría set en lugar de unordered_set?

145

Se presenta C ++ 0x, unordered_setque está disponible en boostmuchos otros lugares. Lo que entiendo es que unordered_setes una tabla hash con O(1)complejidad de búsqueda. Por otro lado, setno es más que un árbol con log(n)complejidad de búsqueda. ¿Por qué demonios usaría alguien en setlugar de unordered_set? es decir, ¿hay una necesidad de setmás?

AraK
fuente
22
Su pregunta es fundamentalmente si existe la necesidad de un árbol.
Vinko Vrsalovic
2
Creo que lo dije claramente en la primera línea, que esta es una pregunta estúpida. Me faltaba algo y ahora recibí la respuesta :)
AraK
2
La verdadera razón es que las cosas no son tan en blanco y negro como parecen. Hay muchos grises y otros colores en el medio. Debe recordar que estos contenedores son herramientas. A veces el rendimiento no es crucial y la conveniencia es mucho más significativa. Si todas las personas buscaron la solución más eficiente que "d nunca use C ++ (por no hablar de Python) en el primer lugar y de forma continua y escribir código Optimizar en lenguaje de máquina.
AturSams
(¿Por qué en la tierra cualquier persona utilice un nombre genérico para una aplicación / interfaz con promesas más allá de los implicados por ese nombre, creando una situación incómoda para los sin?)
anciano

Respuestas:

219

Cuando, para alguien que quiere iterar sobre los elementos del conjunto, el orden es importante.

sombra de Luna
fuente
¿Se ordena de acuerdo con el orden de inserción, o de acuerdo con la comparación real utilizando operadores < >?
SomethingSomething
2
Se ordena usando std :: less por defecto; puede anular esto y proporcionar su propio operador de comparación. cplusplus.com/reference/set/set
moonshadow
O a veces cuando solo desea iterar, incluso si el orden no importa.
mfnx
319

Los conjuntos desordenados tienen que pagar por su tiempo de acceso promedio O (1) de varias maneras:

  • setusa menos memoria que unordered_setpara almacenar la misma cantidad de elementos.
  • Para una pequeña cantidad de elementos , las búsquedas en un setpueden ser más rápidas que las búsquedas en un unordered_set.
  • A pesar de que muchas operaciones son más rápidas en el caso promedio para unordered_set, a menudo son la garantía de tener mejores peores complejidades de casos de set(por ejemplo insert).
  • Eso set ordena los elementos es útil si desea acceder a ellos en orden.
  • Puede comparar lexicográfico diferentes sets con <, <=, >y >=. unordered_setNo es necesario que respalden estas operaciones.

algo
fuente
9
+1, todos los puntos excelentes. Las personas tienden a pasar por alto el hecho de que las tablas hash tienen O (1) tiempo promedio de acceso a casos , lo que significa que ocasionalmente pueden tener grandes retrasos. La distinción puede ser importante para los sistemas en tiempo real.
j_random_hacker
Puntos positivos , sin embargo aquí ( en.cppreference.com/w/cpp/container/unordered_set/operator_cmp ) se afirma que podemos comparar los conjuntos no ordenados.
Michiel heit het Broek
55
Definir un "pequeño número de elementos"
Sunjay Varma
44
@SunjayVarma generalmente 100 elementos es un buen límite entre los dos. En caso de duda, nada puede reemplazar el rendimiento de prueba de los dos en su caso de uso específico.
Nate
3
@MichieluithetBroek Solo se establece la comparación de igualdad, no se ordena ( <).
lisyarus
26

Siempre que prefiera un árbol a una tabla hash.

Por ejemplo, las tablas hash son "O (n)" en el peor de los casos. O (1) es el caso promedio. Los árboles son "O ( log n)" en el peor de los casos.

Mehrdad Afshari
fuente
18
/ Equilibrado / árboles son O (ln n) en el peor de los casos. Puede terminar con árboles O (n) (esencialmente listas vinculadas).
extraño
55
Si puede escribir una función hash razonablemente inteligente, casi siempre puede obtener O (1) perf de una tabla hash. Si no puede escribir dicha función hash si necesita iterar "en orden" sobre su conjunto, entonces debe usar un árbol. Pero no deberías usar un árbol porque tienes miedo del "O (n) peor desempeño".
Justin L.
66
escenificador: Para ser pedante, sí. Sin embargo, estamos hablando de establecer en C ++, que generalmente se implementa como un árbol de búsqueda binario equilibrado . Deberíamos haber especificado la operación real para hablar sobre la complejidad. En este contexto, es obvio que estamos hablando de búsqueda.
Mehrdad Afshari
1
Justin L: Es solo una de las razones por las que prefieres un árbol. El núcleo de mi respuesta es la primera línea. Siempre que prefiera una estructura de datos de árbol a una tabla hash. Hay muchos casos en los que los árboles son preferibles a las tablas hash. Las tablas hash particularmente apestan en cosas como "intersecciones de rango".
Mehrdad Afshari
2
Los árboles stl son árboles rojo-negros casi universalmente implementados, un árbol avanzado de auto-equilibrio. Realmente hay casos en los que O (n) buscar en el peor de los casos no es aceptable. Un servicio web que proporcione una interfaz para almacenar valores de usuario no debe usar un mapa hash, ya que un usuario malintencionado podría crear efectivamente un DoS almacenando valores especialmente diseñados. Los sistemas críticos y sensibles al tiempo también pueden no permitir la búsqueda de O (n), el control del tráfico aéreo, etc. Aunque en general tiene razón, use los mapas hash de forma predeterminada y solo cambie la versión del árbol cuando tenga una necesidad real.
deft_code
14

Use set cuando:

  1. Necesitamos datos ordenados (elementos distintos).
  2. Tendríamos que imprimir / acceder a los datos (en orden ordenado).
  3. Necesitamos predecesor / sucesor de elementos.

Use unordered_set cuando:

  1. Necesitamos mantener un conjunto de elementos distintos y no se requiere ordenar.
  2. Necesitamos acceso a un solo elemento, es decir, no transversal.

Ejemplos:

conjunto:

Entrada: 1, 8, 2, 5, 3, 9

Salida: 1, 2, 3, 5, 8, 9

Conjunto_desordenado:

Entrada: 1, 8, 2, 5, 3, 9

Salida: 9 3 1 8 2 5 (tal vez este orden, influenciado por la función hash)

Principalmente diferencia:

ingrese la descripción de la imagen aquí

Nota: (en algunos casos setes más conveniente), por ejemplo, usar vectorcomo clave

set<vector<int>> s;
s.insert({1, 2});
s.insert({1, 3});
s.insert({1, 2});

for(const auto& vec:s)
    cout<<vec<<endl;   // I have override << for vector
// 1 2
// 1 3 

La razón por la que vector<int>puede ser clave es setporque se vectoranula operator<.

Pero si lo usa unordered_set<vector<int>>, debe crear una función hash vector<int>, porque el vector no tiene una función hash, por lo que debe definir una como:

struct VectorHash {
    size_t operator()(const std::vector<int>& v) const {
        std::hash<int> hasher;
        size_t seed = 0;
        for (int i : v) {
            seed ^= hasher(i) + 0x9e3779b9 + (seed<<6) + (seed>>2);
        }
        return seed;
    }
};

vector<vector<int>> two(){
    //unordered_set<vector<int>> s; // error vector<int> doesn't  have hash function
    unordered_set<vector<int>, VectorHash> s;
    s.insert({1, 2});
    s.insert({1, 3});
    s.insert({1, 2});

    for(const auto& vec:s)
        cout<<vec<<endl;
    // 1 2
    // 1 3
}

Puedes ver que en algunos casos unordered_setes más complicado.

Principalmente citado de: https://www.geeksforgeeks.org/set-vs-unordered_set-c-stl/ https://stackoverflow.com/a/29855973/6329006

Jayhello
fuente
6

Porque std :: set es parte de Standard C ++ y unordered_set no lo es. C ++ 0x NO es un estándar, y tampoco lo es Boost. Para muchos de nosotros, la portabilidad es esencial, y eso significa apegarse al estándar.


fuente
2
Si lo entiendo correctamente, no está preguntando por qué la gente todavía usa set. Se está informando sobre C ++ 0x.
Johannes Schaub - litb
2
Tal vez. Pensé que todos sabían que las tablas hash y los árboles resolvían diferentes problemas.
21
Bueno, es un estándar ahora (solo tomó unos años)
Clayton Hughes
6

Considere algoritmos de línea de barrido. Estos algoritmos fallarían por completo con las tablas hash, pero funcionan maravillosamente con árboles equilibrados. Para darle un ejemplo concreto de un algoritmo de línea de barrido, considere el algoritmo de la fortuna. http://en.wikipedia.org/wiki/Fortune%27s_algorithm

ldog
fuente
1
Creo que dicha referencia es demasiado compleja dada la pregunta. (Tuve que buscarlo)
hectorpal
3

Una cosa más, además de lo que otras personas ya mencionaron. Aunque la complejidad amortizado espera para la inserción de un elemento a un unordered_set es O (1), de vez en cuando se va a tomar O (n), ya que las necesidades tabla hash a reestructurarse (el número de necesidades cubos para el cambio) - incluso con una función hash 'buena'. Al igual que insertar un elemento en un vector toma O (n) de vez en cuando porque la matriz subyacente necesita ser reasignada.

Insertar en un conjunto siempre toma como máximo O (log n). Esto podría ser preferible en algunas aplicaciones.

Blargle
fuente
3

Disculpe, una cosa más que vale la pena notar sobre la propiedad ordenada:

Si desea un rango de datos en el contenedor, por ejemplo: almacenó el tiempo en el conjunto y desea tiempo del 2013-01-01 al 2014-01-01.

Para un_order_set es imposible.

Por supuesto, este ejemplo sería más convincente para casos de uso entre map y unordered_map .

Espectral
fuente
3

g++ 6.4 stdlibc ++ comparado con el conjunto de referencia desordenado

Comparé esta implementación dominante de Linux C ++ para ver la diferencia:

ingrese la descripción de la imagen aquí

Los detalles y análisis completos de referencia se han proporcionado en: ¿Cuál es la estructura de datos subyacente de un conjunto STL en C ++? y no los repetiré aquí.

"BST" significa "probado con std::sety" hash map "significa" probado con std::unordered_set. "Heap" es para std::priority_queuelo que analicé en: Heap vs Binary Search Tree (BST)

Como resumen rápido:

  • el gráfico muestra claramente que, en estas condiciones, la inserción de hashmap siempre fue mucho más rápida cuando hay más de 100 000 elementos, y la diferencia aumenta a medida que aumenta el número de elementos

    El costo de este aumento de velocidad es que no puede atravesar eficientemente en orden.

  • las curvas sugieren claramente que ordenado std::setestá basado en BST y std::unordered_setestá basado en hashmap. En la respuesta de referencia, confirmé que mediante el paso GDB depurando el código.

Pregunta similar para mapvs unordered_map: ¿Hay alguna ventaja de usar map sobre unordered_map en caso de claves triviales?

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
1

Por supuesto, diría que es conveniente tener cosas en una relación si está buscando convertirlo a un formato diferente.

También es posible que aunque uno sea más rápido para acceder, el tiempo para construir el índice o la memoria utilizada al crearlo y / o acceder a él es mayor.

Rushyo
fuente
+1, la notación Big Oh oculta los factores constantes y, para los tamaños de problemas típicos, a menudo son los factores constantes los que más importan.
j_random_hacker
1

Si desea ordenar las cosas, entonces usaría set en lugar de unordered_set. unordered_set se usa sobre el conjunto cuando el pedido almacenado no importa.

leiz
fuente
1

Si bien esta respuesta puede demorar 10 años, vale la pena señalar que std::unordered_settambién tiene desventajas de seguridad.

Si la función hash es predecible (este suele ser el caso a menos que aplique contramedidas como una sal aleatoria), los atacantes pueden crear datos a mano que produzcan colisiones hash y provoquen que todas las inserciones y búsquedas tomen tiempo O (n) .

Esto se puede usar para ataques de denegación de servicio muy eficientes y elegantes.

Muchas (¿la mayoría?) Implementaciones de lenguajes que emplean internamente mapas hash se han encontrado con esto:

ratones
fuente