Buscando una implementación establecida con poca huella de memoria

9

Estoy buscando la implementación del tipo de datos establecido. Es decir, tenemos que

  • mantener un subconjunto dinámico (de tamaño ) del universo de tamaño u connSnuU={0,1,2,3,,u1}u
  • operaciones insert(x)(agregue un elemento xa S ) y find(x)(verifica si el elemento xes miembro de S ).

No me importan otras operaciones. Para orientación, en las aplicaciones con las que estoy trabajando tenemos u1010 .

Sé de implementaciones que proporcionan ambas operaciones en el tiempo O(1) , por lo que me preocupa principalmente el tamaño de la estructura de datos. Espero miles de millones de entradas, pero quiero evitar el intercambio tanto como sea posible.

Estoy dispuesto a sacrificar el tiempo de ejecución si es necesario. El tiempo de ejecución amortizado de O(logn) es lo que puedo admitir; los tiempos de ejecución esperados o los tiempos de ejecución en ω(logn) no son admisibles.

Una idea que tengo es que si S puede representarse como una unión de rangos [xmin, xmax], podremos ahorrar en el tamaño de almacenamiento con el precio de una disminución en el rendimiento. Además, algunos otros patrones de datos son posibles, como [0, 2, 4, 6].

¿Podría indicarme estructuras de datos que puedan hacer algo así?

HEKTO
fuente
¿Cómo entra el número de elementos en la imagen? Es decir, ¿qué sucede si se inserta un elemento y ya hay ? nnn
vonbrand
@vonbrand: nes el tamaño del conjunto S. Puede aumentar con cada inserto puede permanecer igual si el elemento xya está en el conjunto.
HEKTO
3
¿Puedes aceptar una pequeña probabilidad de falsos positivos? Si es así, un filtro de floración podría ser ideal: en.wikipedia.org/wiki/Bloom_filter
Joe
1
@AlekseyYakovlev, la tasa de falsos positivos de un filtro de floración no tiene nada que ver con el tamaño del universo (solo con el número de funciones hash , el tamaño de la estructura de datos el número de elementos ), pero si realmente es cerca de (digamos para una pequeña constante ), creo que será difícil hacerlo mejor que un simple vector de bits, creo, con solo bits totales de espacio. m n n u u = n kmnnuc c nu=ncccn
Joe

Respuestas:

8

La respuesta de Joe es extremadamente buena y le brinda todas las palabras clave importantes.

Debe tener en cuenta que la investigación sucinta de la estructura de datos aún se encuentra en una etapa temprana, y muchos de los resultados son en gran medida teóricos. Muchas de las estructuras de datos propuestas son bastante complejas de implementar, pero la mayor parte de la complejidad se debe al hecho de que necesita mantener la complejidad asintótica tanto en el tamaño del universo como en la cantidad de elementos almacenados. Si cualquiera de estos es relativamente constante, entonces gran parte de la complejidad desaparece.

Si la colección es semiestática (es decir, las inserciones son raras, o al menos de bajo volumen), entonces vale la pena considerar una estructura de datos estáticos fácil de implementar (el sdarray de Sadakane es una buena opción) junto con una actualización cache. Básicamente, registra las actualizaciones en una estructura de datos tradicional (por ejemplo, B-tree, trie, tabla hash) y actualiza periódicamente la estructura de datos "principal". Esta es una técnica muy popular en la recuperación de información, ya que los índices invertidos tienen muchas ventajas para la búsqueda, pero son difíciles de actualizar en el lugar. Si este es el caso, hágamelo saber en un comentario y enmendaré esta respuesta para darle algunos consejos.

Si las inserciones son más frecuentes, entonces sugiero un hash sucinto. La idea básica es lo suficientemente sencilla como para explicar aquí, así que lo haré.

Entonces, el resultado teórico de la información básica es que si está almacenando elementos de un universo de elementos , y no hay otra información (por ejemplo, ninguna correlación entre los elementos), entonces necesita bits para almacenarlo. (Todos los logaritmos son de base 2 a menos que se especifique lo contrario). Necesita esta cantidad de bits. No hay manera de evitarlo. log ( unulog(un)+O(1)

Ahora algo de terminología:

  • Si tiene una estructura de datos que puede almacenar los datos y respaldar sus operaciones en bits de espacio, llamaremos a esto una estructura de datos implícita .log(un)+O(1)
  • Si tiene una estructura de datos que puede almacenar los datos y respaldar sus operaciones en bits de espacio, llamamos a esto una estructura de datos compacta . Tenga en cuenta que en la práctica esto significa que la sobrecarga relativa (relativa al mínimo teórico) está dentro de una constante. Podría ser un 5% de gastos generales, o un 10% de gastos generales, o 10 veces de gastos generales.log(un)+O(log(un))=(1+O(1))log(un)
  • Si tiene una estructura de datos que puede almacenar los datos y respaldar sus operaciones en bits de espacio, llamamos a esto una estructura de datos sucinta .log(un)+o(log(un))=(1+o(1))log(un)

La diferencia entre sucinto y compacto es la diferencia entre little-oh y big-oh. Ignorando el valor absoluto por un momento ...

  • c n 0 n > n 0 g ( n ) < c f ( n )g(n)=O(f(n)) significa que existe una constante y un número tal que para todos , .cn0n>n0g(n)<cf(n)
  • c n 0 n > n 0 g ( n ) < c f ( n )g(n)=o(f(n)) significa que para todas las constantes existe un número tal que para todas , .cn0n>n0g(n)<cf(n)

Informalmente, big-oh y little-oh están "dentro de un factor constante", pero con big-oh la constante es elegida por usted (por el diseñador del algoritmo, el fabricante de la CPU, las leyes de la física o lo que sea), pero con -oh, eliges la constante y puede ser tan pequeña como quieras . En otras palabras, con estructuras de datos sucintas, la sobrecarga relativa se reduce arbitrariamente a medida que aumenta el tamaño del problema.

Por supuesto, el tamaño del problema puede ser enorme para darse cuenta de la sobrecarga relativa que desea, pero no puede tenerlo todo.

Bien, con eso bajo nuestros cinturones, pongamos algunos números en el problema. Supongamos que las claves son enteros de bits (por lo que el tamaño del universo es ), y queremos almacenar de estos enteros. Supongamos que podemos organizar mágicamente una tabla hash idealizada con ocupación total y sin desperdicio, por lo que necesitamos exactamente ranuras hash.2 n 2 m 2 mn2n2m2m

Una operación de búsqueda hash la clave de bits , enmascara bits para encontrar las ranuras de hash y luego verifica si el valor en la tabla coincide con la clave. Hasta aquí todo bien.mnm

Tal tabla hash usa bits. ¿Podemos hacerlo mejor que esto?n2m

Suponga que la función hash es invertible. Entonces no tenemos que almacenar la clave completa en cada ranura de hash. La ubicación de la ranura de hash le da bits del valor de hash, por lo que si solo almacenó los restantes, puede reconstruir la clave a partir de esas dos piezas de información (la ubicación de la ranura de hash y el valor almacenado allí). Por lo tanto, solo necesitaría bits de almacenamiento.m n - m ( n - m ) 2 mhmnm(nm)2m

Si es pequeño en comparación con , la aproximación de Stirling y una pequeña aritmética (¡la prueba es un ejercicio!) Revela que:2 n2m2n

(nm)2m=log(2n2m)+o(log(2n2m))

Entonces esta estructura de datos es sucinta.

Sin embargo, hay dos capturas.

La primera trampa es construir funciones hash invertibles "buenas". Afortunadamente, esto es mucho más fácil de lo que parece; los criptógrafos hacen funciones invertibles todo el tiempo, solo que los llaman "cifrados". Podría, por ejemplo, basar una función hash en una red Feistel, que es una forma sencilla de construir funciones hash invertibles a partir de funciones hash no invertibles.

El segundo inconveniente es que las tablas hash reales no son ideales, gracias a la paradoja del cumpleaños. Por lo tanto, querrá utilizar un tipo de tabla hash más sofisticado que lo acerque a la ocupación total sin derrames. El hash de cuco es perfecto para esto, ya que te permite acercarte arbitrariamente al ideal en teoría, y bastante cerca en la práctica.

El hash de Cuckoo requiere múltiples funciones hash, y requiere que los valores en las ranuras hash se etiqueten con la función hash utilizada. Entonces, si usa cuatro funciones hash, por ejemplo, necesita almacenar dos bits adicionales en cada ranura hash. Esto sigue siendo sucinto a medida que crece, por lo que no es un problema en la práctica, y aún supera el almacenamiento de claves completas.m

Ah, también es posible que desee mirar los árboles de van Emde Boas.

MÁS PENSAMIENTOS

Si está en algún lugar alrededor de , entonces es aproximadamente , entonces (una vez más) suponiendo que no hay más correlación entre los valores, básicamente no puede hacer nada mejor que un vector de bits Notará que la solución de hash anterior degenera efectivamente en ese caso (termina almacenando un bit por ranura de hash), pero es más barato usar la clave como la dirección en lugar de usar una función de hash.un log ( uu2 ulog(un)u

Si está muy cerca de , toda la literatura de estructuras de datos sucintas le aconseja que invierta el sentido del diccionario. Almacene los valores que no ocurren en el conjunto. Sin embargo, ahora debe admitir efectivamente la operación de eliminación, y para mantener un comportamiento breve también debe poder reducir la estructura de datos a medida que se "agregan" más elementos. Expandir una tabla hash es una operación bien entendida, pero contraerla no lo es.unu

Seudónimo
fuente
Hola, en cuanto al segundo párrafo de su respuesta: espero que cada llamada a insertvaya acompañada de una llamada a findcon el mismo argumento. Entonces, si el findretorno true, entonces simplemente nos saltamos el insert. Entonces, la frecuencia de las findllamadas es más que la frecuencia de las insertllamadas, también cuando nse acerca u, entonces las insertllamadas se vuelven muy raras.
HEKTO
Pero esperas acercarse a con el tiempo? nun
Seudónimo
En el mundo real, n está creciendo hasta llegar a ti, sin embargo, no podemos predecir si sucederá o no. La estructura de datos debería funcionar bien para cualquieran <= u
HEKTO
Derecha. Entonces es justo decir que no conocemos una sola estructura de datos que sea sucinta (en el sentido anterior) y que logre esto en todo el rango de . Creo que va a querer una estructura de datos dispersa cuando , luego cambie a una densa (por ejemplo, un vector de bits) cuando esté alrededor de , luego una estructura de datos dispersa con una inversión sentir cuando está cerca de . n<ununun<un nuu2nu
Seudónimo
5

Parece que quiere una estructura de datos sucinta para el problema de la membresía dinámica .

Recuerde que una estructura de datos sucinta es aquella para la cual el requisito de espacio está "cerca" del límite inferior teórico de la información, pero a diferencia de una estructura de datos comprimida, aún permite consultas eficientes.

El problema de la membresía es exactamente lo que usted describe en su pregunta:

mantener un subconjunto (de tamaño ) del universo de tamaño con operaciones:n U = { 0 , 1 , 2 , 3 , ... , u - 1 } uSnU={0,1,2,3,,u1}u

  • find(x)(comprueba si el elemento xes miembro de ).S
  • insert(x)(agregue un elemento xa )S
  • delete(x)(eliminar un elemento xde )S

Si solo findse admite la operación, entonces este es el problema de la membresía estática . Si se admite inserto deleteno, pero no ambos, se llama semi-dinámico , y si se admiten las tres operaciones, se denomina problema de membresía dinámica .

Técnicamente, creo que solo solicitó una estructura de datos para el problema de membresía semidinámica, pero no conozco ninguna estructura de datos que aproveche esta restricción y también cumpla con sus otros requisitos. Sin embargo, tengo la siguiente referencia:

En el Teorema 5.1 del artículo Membresía en tiempo constante y espacio casi mínimo , Brodnik y Munro dan el siguiente resultado:

Existe una estructura de datos que requiere bits que admite búsquedas en tiempo constante e inserciones y eliminaciones en tiempo amortizado esperado constante.O(B)

donde es el número mínimo teórico de información requerido de bits.B=log(un)

La idea básica es que dividen recursivamente el universo en rangos de tamaños cuidadosamente elegidos, por lo que esto incluso parece que las técnicas podrían estar en la línea que estás pensando.

Sin embargo, si está buscando algo que realmente pueda implementar, no sé si esta será su mejor opción. Solo leí el documento, e intentar explicar los detalles está más allá del alcance de esta respuesta. Parametrizan su solución, usando diferentes estrategias dependiendo de los tamaños relativos de y . Y la versión dinámica de la estructura de datos solo se bosqueja en el documento.nun

Joe
fuente
1
El resumen en papel de Brodnik y Munro no dice nada sobre los insertos. Pero su resultado es lo que podemos esperar, ¿verdad? Si n = u/2, entonces el espacio necesario es máximo.
HEKTO
@AlekseyYakovlev Realmente no mencionan el caso dinámico en el resumen, pero el teorema que trata el caso dinámico se cita en mi respuesta (de la sección 5).
Joe