¿Por qué los conjuntos en Python tienen una complejidad algorítmica de O (1)?

Una tabla hash tiene una complejidad de tiempo esperada para la inserción, eliminación y verificación de membresía que es constante en el número de entradas que se almacenan. El conjunto de Python se basa en una implementación de tabla hash.

Pero esto oculta algunos supuestos que pueden ser violados en la práctica.

La inserción de tiempo constante supone que podemos amortizar el costo de cambiar el tamaño de la tabla hash en muchas inserciones. Por ejemplo, si duplicamos el tamaño de la tabla hash cada vez que necesitamos aumentarla, entonces el costo por inserto es constante, aunque algunos de los insertos pueden ser bastante caros. Entonces, la complejidad de tiempo O (1) en realidad significa O (1) complejidad de tiempo amortizada , mientras que el peor de los casos sigue siendo O (n).

La verificación de membresía de tiempo constante supone que hay un tamaño máximo de clave, pero en realidad la operación “en” puede depender del tamaño de los elementos en el conjunto. Realicé un experimento simple en mi computadora portátil en el que creé un conjunto de 3 elementos de enteros largos de tamaño creciente, y medí el tiempo que tardó en verificar la membresía de un elemento en el conjunto, y uno no en el conjunto:

10e5 bits: 6.7e-7 segundos cuando está presente, 5.9e-7 segundos cuando no está presente

10e6 bits: 5.1e-6 / 5.1e-6

10e7 bits: 5.0e-5 / 5.0e-5

10e8 bits: 5.1e-4 / 5.1e-4

Es decir, un aumento de 10 veces en el tamaño de la clave condujo a un aumento de 10 veces en el tiempo necesario para establecer la membresía.

Un conjunto de elementos [math] n [/ math] debe usar al menos [math] log_2 n [/ math] bits para identificar cada elemento, lo que significa que la búsqueda de membresía es en realidad [math] O (\ log n) [/ math ], porque requiere una comparación. Pero a veces simplemente ignoramos esto o decimos que la verificación de membresía es [matemática] O (k) [/ matemática] donde [matemática] k [/ matemática] es el tamaño de la clave.

Finalmente, la complejidad de tiempo esperada [matemática] O (1) [/ matemática] asume un modelo no contencioso en el que las claves se distribuyen aleatoriamente (o aleatoriamente después del hash), no elegidas por un oponente con conocimiento de la función hash. Consulte Generación de colisiones hash de 64 bits para DOS Python – Robert Grosse – Medio. Python 3.3 activa la aleatorización de hash de forma predeterminada, que intenta combatir esta forma de ataque, y Python 3.4 cambió a una función hash más segura.

La estructura de datos se basa en una tabla hash.

Es extremadamente común en la industria de la computación tener cierta flexibilidad con respecto a lo que se trata esta notación big-O, por lo que generalmente se saldrá con la suya de que la inserción, la búsqueda y la eliminación de una tabla hash son O (1).

También es común decir la palabra “amortizado” en este contexto. También está mal, pero por cualquier razón, las personas extremadamente inteligentes no lo llamarán.

Lo que podría ser mejor es ser ondulado a mano y decir “para todos los fines prácticos”, pero lo que está realmente aquí es decir “esperado” O (1) o simplemente “promedio” O (1) (sin incluir letras griegas que pocas personas usan).

El O promedio (1) o el O esperado (1) es un superconjunto de O amortizado (1), y el O amortizado (1) es un superconjunto del peor de los casos O (1). Aquí hay unos ejemplos.

Agregar dos números de tamaño de palabra (digamos, en los registros) sería el peor de los casos O (1). Dado un modelo idealizado de computación, la operación siempre se completará en una serie de pasos que están limitados por una constante.

Anexar a una matriz de tamaño variable se puede hacer que se amortice O (1). Esto significa que, incluso en el peor de los casos, puede hacer la contabilidad (pasos comerciales de acciones costosas a las baratas) que hace que cada acción “parezca” O (1) … incluso si hay algunas acciones particulares que toman (en este caso ejemplo) peor caso O (n) pasos.

O esperado (1) es más general todavía. Ya no está haciendo una declaración sobre el peor tiempo de ejecución del caso. Está haciendo una declaración sobre cuál es la complejidad de tiempo promedio / esperada de las acciones, si hubiera ejecutado el algoritmo muchas veces con muchas entradas diferentes desde el espacio de posibles entradas. Algunos de ellos podrían ser “casos degenerados” en los que en realidad muestran el peor de los casos O (log n) u O (n) … pero el promedio sobre todas las entradas posibles aún garantiza una complejidad de tiempo O (1) esperada.

Es fácil descartar esto como académico y sin sentido, al menos hasta que se enfrente a un adversario que induce el peor de los casos al martillar su aplicación con claves que se asignan a los mismos cubos en la tabla hash.

Solo puede establecer la complejidad algorítmica de las operaciones.

Un conjunto en Python es un tipo de datos. Admite muchas operaciones diferentes, pero tres de las más importantes son O (1):

  • Prueba de membresía utilizando el operador in
  • Agregar elementos con add
  • Eliminar elementos con remove

Otras operaciones, como unión, intersección y diferencia, son mucho más lentas.

La razón por la que estas operaciones son tan rápidas es porque los conjuntos se implementan utilizando una tabla hash.

Solo la prueba de membresía establecida se amortiza O (1) por la misma razón por la cual la prueba de membresía en una tabla hash siempre se amortiza O (1). Los conjuntos son solo tablas hash.

Vea la respuesta de Ted Scharff a ¿Cómo se configura internamente en Python?

More Interesting

Si hago algunos cálculos iterando sobre el bucle mientras tomo las entradas. A partir de entonces, imprimiendo el resultado. ¿Puedo decir que es O (1) complejidad?

¿Por qué la agrupación aleatoria al iterar sobre ella y cambiarla por un elemento aleatorio entre 0 y el último elemento de la matriz no produce una barajadura distribuida uniformemente?

¿Por qué creas matrices en Java y cuáles son las posibilidades de crear una matriz?

¿Qué árbol captura más CO2, un árbol completamente maduro o un árbol joven de rápido crecimiento?

¿Hay algún uso de algoritmos que se usan en la programación en robótica?

Conozco estructuras de datos y algoritmos. ¿Cómo programo un compilador simple?

¿Cuáles son los ejemplos de implementación de algoritmos de clasificación en Android?

¿Qué opinas de una educación en informática donde el profesor de 'algoritmos y programación' ni siquiera sabe acerca de la notación Big O?

¿Cómo funciona el algoritmo de búsqueda de ciclo de Floyd? ¿De qué manera mover la tortuga al comienzo de la lista vinculada, mientras se mantiene a la liebre en el lugar de reunión, seguido de mover un paso a la vez, hace que se encuentren en el punto de inicio del ciclo?

¿Por qué la complejidad temporal no devuelve el tiempo de ejecución exacto de un algoritmo?

¿Por qué alguien no puede encontrar un algoritmo para la detección de imágenes que funcione mejor que SIFT (Scale Invariant Feature Transform)? ¿De dónde viene exactamente el problema?

¿Cuál es su estructura de datos favorita y por qué?

¿Por qué se ejecuta un análisis de tiempo de un algoritmo llamado asintótico?

¿Qué algoritmos de programación de procesos usa Android?

¿Por qué Lua está diseñado de tal manera que obtener el tamaño de una tabla es O (n) en el tamaño de la tabla?