Posible duplicado:
algoritmo de mediana variable en C
Dado que los enteros se leen de una secuencia de datos. Encuentre la mediana de los elementos leídos hasta ahora de manera eficiente.
Solución que he leído: podemos usar un montón máximo en el lado izquierdo para representar elementos que son menores que la mediana efectiva, y un montón mínimo en el lado derecho para representar elementos que son mayores que la mediana efectiva.
Después de procesar un elemento entrante, el número de elementos en montones difiere como máximo en 1 elemento. Cuando ambos montones contienen el mismo número de elementos, encontramos el promedio de los datos raíz del montón como mediana efectiva. Cuando los montones no están equilibrados, seleccionamos la mediana efectiva de la raíz del montón que contiene más elementos.
Pero, ¿cómo construiríamos un montón máximo y un montón mínimo, es decir, cómo sabríamos la mediana efectiva aquí? Creo que insertaríamos 1 elemento en max-heap y luego el siguiente 1 elemento en min-heap, y así sucesivamente para todos los elementos. Corrígeme si me equivoco aquí.
Respuestas:
Hay varias soluciones diferentes para encontrar la mediana en ejecución de los datos transmitidos, hablaré brevemente sobre ellos al final de la respuesta.
La pregunta es sobre los detalles de una solución específica (solución de almacenamiento dinámico máximo / almacenamiento dinámico mínimo), y cómo se explica la solución basada en almacenamiento dinámico a continuación:
Para los dos primeros elementos, agregue uno más pequeño al maxHeap a la izquierda y uno más grande al minHeap a la derecha. Luego, procese los datos del flujo uno por uno
Luego, en cualquier momento puede calcular la mediana de esta manera:
Ahora hablaré sobre el problema en general como se prometió al comienzo de la respuesta. Encontrar una mediana en ejecución a partir de un flujo de datos es un problema difícil, y encontrar una solución exacta con restricciones de memoria de manera eficiente es probablemente imposible para el caso general. Por otro lado, si los datos tienen algunas características que podemos explotar, podemos desarrollar soluciones especializadas eficientes. Por ejemplo, si sabemos que los datos son un tipo integral, entonces podemos usar el orden de conteo, que puede proporcionarle un algoritmo de tiempo constante de memoria constante. La solución basada en el montón es una solución más general porque también se puede utilizar para otros tipos de datos (dobles). Y, por último, si no se requiere la mediana exacta y una aproximación es suficiente, puede intentar estimar una función de densidad de probabilidad para los datos y estimar la mediana utilizando eso.
fuente
Si no puede guardar todos los elementos en la memoria a la vez, este problema se vuelve mucho más difícil. La solución de almacenamiento dinámico requiere que mantenga todos los elementos en la memoria a la vez. Esto no es posible en la mayoría de las aplicaciones del mundo real de este problema.
En cambio, a medida que ve números, realice un seguimiento del recuento del número de veces que ve cada número entero. Suponiendo enteros de 4 bytes, eso es 2 ^ 32 cubos, o como máximo 2 ^ 33 enteros (clave y recuento para cada int), que es 2 ^ 35 bytes o 32 GB. Es probable que sea mucho menos que esto porque no necesita almacenar la clave o contar las entradas que son 0 (es decir, como un defaultdict en python). Esto lleva tiempo constante para insertar cada nuevo entero.
Luego, en cualquier punto, para encontrar la mediana, simplemente use los recuentos para determinar qué número entero es el elemento medio. Esto lleva un tiempo constante (aunque sea una constante grande, pero no obstante constante).
fuente
Si la varianza de la entrada está distribuida estadísticamente (por ejemplo, normal, log-normal, etc.), entonces el muestreo de yacimientos es una forma razonable de estimar percentiles / medianas a partir de una secuencia de números arbitrariamente larga.
"reservorio" es entonces una muestra continua, uniforme (regular) de todas las entradas, independientemente de su tamaño. Encontrar la mediana (o cualquier percentil) es entonces una cuestión directa de clasificar el depósito y sondear el punto interesante.
Como el depósito tiene un tamaño fijo, se puede considerar que la clasificación es efectivamente O (1), y este método se ejecuta con un consumo constante de tiempo y memoria.
fuente
La forma más eficiente de calcular el percentil de una secuencia que he encontrado es el algoritmo P²: Raj Jain, Imrich Chlamtac: El algoritmo P² para el cálculo dinámico de cuantiiles e histogramas sin almacenar observaciones. Commun. ACM 28 (10): 1076-1085 (1985)
El algoritmo es sencillo de implementar y funciona extremadamente bien. Sin embargo, es una estimación, así que tenlo en cuenta. Del resumen:
fuente
Si queremos encontrar la mediana de los n elementos vistos más recientemente, este problema tiene una solución exacta que solo necesita guardar los n elementos vistos más recientemente en la memoria. Es rápido y escala bien.
Una lista de salto indexable admite la inserción, eliminación y búsqueda indexada O (ln n) de elementos arbitrarios mientras se mantiene el orden ordenado. Cuando se combina con una cola FIFO que rastrea la enésima entrada más antigua, la solución es simple:
Aquí hay enlaces para completar el código de trabajo (una versión de clase fácil de entender y una versión optimizada del generador con el código indexable de la lista de omisión incluido):
http://code.activestate.com/recipes/576930-efficient-running-median-using-an-indexable-skipli/
http://code.activestate.com/recipes/577073 .
fuente
Una forma intuitiva de pensar en esto es que si tuviera un árbol de búsqueda binario completamente equilibrado, entonces la raíz sería el elemento mediano, ya que habría la misma cantidad de elementos más pequeños y más grandes. Ahora, si el árbol no está lleno, este no será el caso, ya que faltarán elementos del último nivel.
Entonces, lo que podemos hacer es tener la mediana y dos árboles binarios balanceados, uno para elementos menores que la mediana y otro para elementos mayores que la mediana. Los dos árboles deben mantenerse al mismo tamaño.
Cuando obtenemos un nuevo entero del flujo de datos, lo comparamos con la mediana. Si es mayor que la mediana, la agregamos al árbol correcto. Si los dos tamaños de árbol difieren más de 1, eliminamos el elemento min del árbol derecho, lo convertimos en la nueva mediana y colocamos la mediana anterior en el árbol izquierdo. Del mismo modo para los más pequeños.
fuente
Eficiente es una palabra que depende del contexto. La solución a este problema depende de la cantidad de consultas realizadas en relación con la cantidad de inserciones. Suponga que está insertando N números y K veces hacia el final en el que estaba interesado en la mediana. La complejidad del algoritmo basado en el montón sería O (N log N + K).
Considere la siguiente alternativa. Coloca los números en una matriz y, para cada consulta, ejecuta el algoritmo de selección lineal (usando el pivote de clasificación rápida, por ejemplo). Ahora tiene un algoritmo con tiempo de ejecución O (KN).
Ahora, si K es suficientemente pequeño (consultas poco frecuentes), el último algoritmo es en realidad más eficiente y viceversa.
fuente
¿No puedes hacer esto con un solo montón? Actualización: no. Ver el comentario
Invariante: después de leer las
2*n
entradas, el montón mínimo contiene lan
mayor de ellas.Bucle: Leer 2 entradas. Agréguelos al montón y elimine el mínimo del montón. Esto restablece lo invariante.
Entonces, cuando
2n
se han leído las entradas, el min del montón es el enésimo más grande. Deberá haber una pequeña complicación adicional para promediar los dos elementos alrededor de la posición media y para manejar consultas después de un número impar de entradas.fuente