Algoritmo rapido per la ricerca in una matrice ordinata di float per trovare la coppia di float che racchiude un valore di input


10

Ho una serie di float, ordinati dal più piccolo al più grande, e ho bisogno di essere in grado di scegliere il float più vicino maggiore o minore di un valore di input passato. Questo valore di input non è necessariamente presente come valore nella matrice.

Un approccio ingenuo sarebbe quello di fare una semplice ricerca lineare attraverso l'array. Potrebbe apparire così:

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];
        }
    }
}

Ma ovviamente man mano che l'array diventa più grande, questo diventerà sempre più lento.

Qualcuno ha un'idea di un algoritmo che mi permetta di trovare questi dati in modo più ottimale? Sono già passato a una ricerca binaria, che ha migliorato un po 'le cose, ma è ancora molto più lento di quanto mi piacerebbe, e dal momento che non sto effettivamente cercando un valore specifico che esiste nell'array, non può mai terminare presto.

Ulteriori informazioni: i valori in virgola mobile nell'array non sono necessariamente distribuiti uniformemente (ovvero, l'array potrebbe essere costituito dai valori "1.f, 2.f, 3.f, 4.f, 100.f, 1200.f , 1203.f, 1400.f ".

Sto eseguendo questa operazione centinaia di migliaia di volte, ma posso eseguire qualsiasi quantità di pre-elaborazione sull'array di float, se migliorerà il tempo di ricerca. Posso assolutamente cambiare per usare qualcosa di diverso da un vettore per memorizzarli, se questo mi aiuterà.


Cosa ti fa pensare che la tua ricerca binaria non possa terminare in anticipo? Sicuramente puoi semplicemente testare gli elementi su i e i + 1 per vedere se racchiudono il valore target e terminare se lo fanno?
Paul R,

In alternativa, potrei testare gli elementi su i e i-1 per vedere se racchiudono il valore target. Avrei anche bisogno di testare se 'i' era> = array.size () - 1 in modo da poter evitare di fare il test e se fosse <= 0 in modo da poter evitare di fare il mio test ... in realtà è un sacco di condizionali extra da eseguire ad ogni passo, al fine di verificare una partenza anticipata. Immagino che rallenterebbero molto l'algoritmo, anche se confesso che non l'ho ancora profilato.
Trevor Powell,

3
Non deve essere così complicato - se il tuo array è di dimensioni N, allora devi solo trattarlo come se fosse di dimensioni N - 1. In questo modo c'è sempre un elemento valido in i + 1. Fai un ricerca binaria su N - 1 elemento per elemento i che è inferiore al valore target, con elemento i + 1 maggiore del valore target.
Paul R

Risposte:


11

Il codice nella domanda (una ricerca lineare), come giustamente fai notare, diventerà lento per grandi array float. Tecnicamente è O (n) dove n è il numero di valori float nell'array.

In generale, il meglio che puoi fare per trovare un valore in un array ordinato è una ricerca ad albero ricorsiva di qualche tipo (ad esempio ricerca binaria), nel qual caso puoi ottenere un tempo di ricerca O (log n) nel numero di elementi nel tuo array. O (log n) è molto meglio di O (n) per grandi valori di n.

Il mio approccio suggerito sarebbe quindi una semplice ricerca binaria dell'array , ovvero:

  1. Impostare gli indici interi min / max per coprire l'intero array float
  2. verifica il valore al centro dell'intervallo con l'indice mid = (min + max / 2) rispetto al valore di ricerca x
  3. se x è inferiore a questo valore, imposta max su mid, altrimenti imposta min su mid
  4. ripetere (2-4) fino a quando non si è trovato il valore corretto

Questo è un algoritmo O (log n) che dovrebbe essere abbastanza veloce per quasi tutte le situazioni. Intuitivamente, funziona dimezzando l'intervallo da cercare in ogni passaggio fino a trovare il valore corretto.

È davvero difficile migliorare la semplice ricerca binaria, quindi se lo hai già implementato correttamente, potresti essere già abbastanza vicino all'ottimale. Tuttavia, se conosci le distribuzioni dei dati e / o hai un intervallo limitato di valori di ricerca (x), ci sono ancora altri trucchi più avanzati che puoi provare:

  • Bucketing : crea bucket (ad es. Per ogni intervallo tra due numeri interi), ognuno dei quali contiene un elenco ordinato più piccolo dei valori float tra i due numeri interi più due valori immediatamente sotto e immediatamente sopra ogni intervallo. È quindi possibile iniziare la ricerca da (trunc (x) +0.5). Questo dovrebbe darti una buona velocità se scegli secchi di dimensioni adeguate (sta effettivamente aumentando il fattore di ramificazione dell'albero .....). Se i numeri interi non funzionano per te, puoi provare a utilizzare secchi di qualche altra precisione a virgola fissa (ad es. Multipli di 1/16).
  • Mappatura bit : se l'intervallo dei possibili valori di ricerca è abbastanza piccolo, è possibile provare a creare una tabella di ricerca di grandi dimensioni indicizzata dal valore bit per bit di x. Questo sarà O (1) ma potresti aver bisogno di molta memoria che sarà molto ostile nella tua cache ... quindi usa con cautela. Questo è particolarmente brutto perché stai cercando valori float, quindi potresti aver bisogno di diversi GB per tenere conto di tutti i bit meno significativi ......
  • Arrotondamento e hash : le tabelle hash probabilmente non sono la migliore struttura di dati per questo problema, ma se riesci a sopravvivere perdendo un po 'di precisione potrebbero funzionare - semplicemente arrotondando i bit più bassi dei tuoi valori di ricerca e usa una hashmap per cercare direttamente valore corretto. Dovrai sperimentare il giusto compromesso tra dimensione hashmap e precisione e assicurarti anche che tutti i possibili valori hash siano popolati in modo che questo possa essere un po 'complicato ......
  • Bilanciamento degli alberi : il tuo albero ideale dovrebbe avere il 50% di probabilità di andare a sinistra oa destra. Pertanto, se si crea un albero in base alla distribuzione dei valori di ricerca (x), è possibile ottimizzare l'albero per produrre risposte con il minimo numero di test. Questa è probabilmente una buona soluzione se molti valori nel tuo array float sono molto vicini tra loro, poiché ti consentiranno di evitare di cercare questi rami troppo spesso.
  • Alberi crit-bit : sono ancora alberi (quindi ancora O (log n) ...) ma alcuni casi: per far funzionare i confronti dovresti comunque convertire i tuoi float in un formato a virgola fissa

Tuttavia, a meno che non ti trovi in ​​una situazione molto speciale, ti consiglio di attenermi alla semplice ricerca binaria. Motivi:

  • è molto più facile da implementare
  • è molto veloce per i casi più comuni
  • l'overhead aggiuntivo degli approcci più complessi (ad es. maggiore utilizzo della memoria / pressione della cache) spesso supera i guadagni teorici minori
  • sarà più robusto per i futuri cambiamenti nelle distribuzioni dei dati ....

1

Questo sembra abbastanza semplice:

Effettua una ricerca binaria per il float che vuoi limitare - O (log n) time.

Quindi l'elemento a sinistra di esso è il limite inferiore e l'elemento a destra di esso è il limite superiore.


0

La risposta ovvia è conservare i galleggianti in un albero . Supportare le operazioni "precedenti" e "successive" è banale in un albero. Quindi fai semplicemente un "prossimo" sul tuo valore, quindi fai un "precedente" sul valore che trovi nel primo passaggio.


1
Questo è essenzialmente lo stesso di una ricerca binaria.
Kevin Cline,

-1

Questo documento ("ricerca sublogaritmica senza moltiplicazioni") potrebbe essere interessante; contiene anche del codice sorgente. Ai fini del confronto, è possibile trattare un numero float come un numero intero con lo stesso modello di bit; questo era uno degli obiettivi di progettazione dello standard IEEE in virgola mobile.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.