Algoritmo rápido para buscar un conjunto ordenado de flotadores para encontrar el par de flotadores entre corchetes de un valor de entrada

10

Tengo una serie de flotadores, ordenados de menor a mayor, y necesito poder elegir el flotador más cercano mayor o menor que un valor de entrada pasado. Este valor de entrada no está necesariamente presente como un valor en la matriz.

Un enfoque ingenuo sería hacer una búsqueda lineal simple a través de la matriz. Eso podría verse así:

void FindClosestFloatsInArray( float input, std::vector<float> array, 
                               float *min_out, float *max_out )
{
    assert( input >= array[0] && input < array[ array.size()-1 ] );
    for( int i = 1; i < array.size(); i++ )
    {
        if ( array[i] >= input )
        {
            *min = array[i-1];
            *max = array[i];
        }
    }
}

Pero obviamente, a medida que la matriz se hace más grande, esto se volverá más y más lento.

¿Alguien tiene una idea sobre un algoritmo que me permita encontrar estos datos de manera más óptima? Ya cambié a una búsqueda binaria, que mejoró un poco las cosas, pero sigue siendo mucho más lenta de lo que me gustaría, y como en realidad no estoy buscando un valor específico que exista en la matriz, nunca puede terminar temprano.

Más información: Los valores de coma flotante en la matriz no se distribuyen necesariamente de manera uniforme (es decir, la matriz podría consistir en los valores "1.f, 2.f, 3.f, 4.f, 100.f, 1200.f , 1203.f, 1400.f ".

Estoy haciendo esta operación cientos de miles de veces, pero puedo hacer cualquier cantidad de preprocesamiento en la matriz de flotadores, si mejora el tiempo de búsqueda. Absolutamente puedo cambiar para usar algo que no sea un vector para almacenarlos, si eso ayuda.

Trevor Powell
fuente
¿Qué te hace pensar que tu búsqueda binaria no puede terminar antes? ¿Seguramente puede probar los elementos en i e i + 1 para ver si están entre paréntesis el valor objetivo y terminar si lo hacen?
Paul R
Alternativamente, podría probar los elementos en i e i-1 para ver si incluyen el valor objetivo. También necesitaría probar si 'i' era> = array.size () - 1 para poder evitar hacer tu prueba, y si era <= 0 para poder evitar hacer mi prueba ... en realidad es mucho condicionales adicionales para realizar en cada paso, a fin de verificar una salida anticipada. Me imagino que ralentizarían mucho el algoritmo, aunque confieso que aún no lo he perfilado.
Trevor Powell
3
No necesita ser tan complicado: si su matriz es de tamaño N, entonces solo debe tratarla como si fuera de tamaño N - 1. De esa manera, siempre hay un elemento válido en i + 1. Usted hace un búsqueda binaria sobre el elemento N - 1 para el elemento i, que es menor que su valor objetivo, siendo el elemento i + 1 mayor que el valor objetivo.
Paul R

Respuestas:

11

El código en la pregunta (una búsqueda lineal), como señala correctamente, se volverá lento para las grandes matrices flotantes. Técnicamente es O (n) donde n es el número de valores flotantes en su matriz.

En general, lo mejor que puede hacer para encontrar un valor en una matriz ordenada es una búsqueda de árbol recursiva de algún tipo (por ejemplo, búsqueda binaria), en cuyo caso puede lograr un tiempo de búsqueda O (log n) en el número de elementos en tu matriz O (log n) es mucho mejor que O (n) para valores grandes de n.

Por lo tanto, mi enfoque sugerido sería una simple búsqueda binaria de la matriz , es decir:

  1. Establezca índices enteros min / max para cubrir toda su matriz flotante
  2. pruebe el valor en el medio del rango en el índice medio = (min + max / 2) contra el valor de búsqueda x
  3. si x es menor que este valor, configure max a mid, de lo contrario, configure min a mid
  4. repita (2-4) hasta que haya encontrado el valor correcto

Este es un algoritmo O (log n) que debería ser lo suficientemente rápido para casi todas las situaciones. Intuitivamente, funciona reduciendo a la mitad el rango que se buscará en cada paso hasta que encuentre el valor correcto.

Es realmente difícil superar la búsqueda binaria simple, por lo que si ya ha implementado esto correctamente, entonces puede estar bastante cerca del óptimo. Sin embargo, si conoce las distribuciones de los datos y / o tiene un rango limitado de valores de búsqueda (x), todavía hay otros trucos más avanzados que puede probar:

  • Agrupamiento : cree depósitos (por ejemplo, para cada intervalo entre dos enteros), cada uno de los cuales contiene una lista ordenada más pequeña de los valores flotantes entre los dos enteros delimitadores más dos valores inmediatamente debajo e inmediatamente por encima de cada rango. Luego puede comenzar su búsqueda en (trunc (x) +0.5). Esto debería darle una buena aceleración si elige cubos de tamaño apropiado (está aumentando efectivamente el factor de ramificación del árbol .....). Si los enteros no funcionan para usted, entonces puede probar cubos de alguna otra precisión de punto fijo (por ejemplo, múltiplos de 1/16).
  • Asignación de bits : si el rango de posibles valores de búsqueda es lo suficientemente pequeño, podría intentar crear una tabla de búsqueda grande indexada por el valor de x de bits. Esto será O (1) pero es posible que necesite mucha memoria, lo que será muy hostil en su caché ... así que úselo con precaución. Esto es especialmente desagradable porque está buscando valores flotantes, por lo que es posible que necesite varios GB para tener en cuenta todos los bits menos significativos ...
  • Redondeo y hash : las tablas hash probablemente no sean la mejor estructura de datos para este problema, pero si puede sobrevivir perdiendo un poco de precisión, podrían funcionar: simplemente redondee los bits más bajos de sus valores de búsqueda y use un mapa hash para buscar directamente el valor correcto Tendrá que experimentar el equilibrio correcto entre el tamaño y la precisión del hashmap, y también asegurarse de que todos los valores de hash posibles se llenen para que esto pueda ser un poco complicado ...
  • Equilibrio de árboles : su árbol ideal debe tener un 50% de posibilidades de ir hacia la izquierda o hacia la derecha. Entonces, si crea un árbol basado en la distribución de valores de búsqueda (x), puede optimizar el árbol para producir respuestas con la mínima cantidad de pruebas. Es probable que sea una buena solución si muchos valores en su matriz flotante están muy juntos, ya que le permitirá evitar buscar estas ramas con demasiada frecuencia.
  • Árboles de bits críticos : siguen siendo árboles (por lo tanto, O (log n) ...) pero en algunos casos: sin embargo, deberá convertir sus flotadores en algún formato de punto fijo para que las comparaciones funcionen

Sin embargo, a menos que se encuentre en una situación muy especial, probablemente le recomiende seguir con la búsqueda binaria simple. Razones:

  • es mucho más fácil de implementar
  • es muy rápido para la mayoría de los casos comunes
  • la sobrecarga adicional de los enfoques más complejos (por ejemplo, mayor uso de memoria / presión de caché) a menudo supera las ganancias teóricas menores
  • será más robusto para futuros cambios en la distribución de datos ...
mikera
fuente
1

Esto parece bastante simple:

Haga una búsqueda binaria para el flotante que desea vincular: tiempo O (log n).

Entonces el elemento a la izquierda es el límite inferior, y el elemento a la derecha es el límite superior.

Ankit Soni
fuente
0

La respuesta obvia es almacenar las carrozas en un árbol . Las operaciones de soporte 'anteriores' y 'siguientes' son triviales en un árbol. Así que solo haga un 'siguiente' en su valor, y luego haga un 'anterior' en el valor que encuentre en el primer paso.

David Schwartz
fuente
1
Esto es esencialmente lo mismo que una búsqueda binaria.
Kevin Cline
-1

Este artículo ("búsqueda sublogarítmica sin multiplicaciones") podría ser de interés; Incluso contiene algo de código fuente. Para fines de comparación, puede tratar un número flotante como un entero con el mismo patrón de bits; Este fue uno de los objetivos de diseño del estándar de coma flotante IEEE.

zvrba
fuente