Schneller Algorithmus zum Durchsuchen eines sortierten Arrays von Floats, um das Float-Paar zu finden, das einen Eingabewert einschließt

10

Ich habe eine Reihe von Floats, die vom kleinsten zum größten sortiert sind, und muss in der Lage sein, den nächsten Float auszuwählen, der größer oder kleiner als ein übergebener Eingabewert ist. Dieser Eingabewert ist nicht unbedingt als Wert im Array vorhanden.

Ein naiver Ansatz wäre eine einfache lineare Suche durch das Array. Das könnte so aussehen:

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

Aber wenn das Array größer wird, wird dies natürlich immer langsamer.

Hat jemand eine Idee zu einem Algorithmus, mit dem ich diese Daten optimaler finden kann? Ich habe bereits zu einer binären Suche gewechselt, die die Dinge etwas verbessert hat, aber sie ist immer noch viel langsamer als ich es gerne hätte, und da ich nicht nach einem bestimmten Wert suche, der im Array vorhanden ist, kann sie niemals beendet werden früh.

Weitere Informationen: Die Gleitkommawerte im Array sind nicht unbedingt gleichmäßig verteilt (dh das Array kann aus den Werten "1.f, 2.f, 3.f, 4.f, 100.f, 1200.f" bestehen 1203.f, 1400f.

Ich mache diesen Vorgang hunderttausend Mal, aber ich kann jede Menge Vorverarbeitung für das Array von Floats durchführen, wenn dies die Suchzeit verbessert. Ich kann mich absolut ändern, um etwas anderes als einen Vektor zum Speichern zu verwenden, wenn das hilft.

Trevor Powell
quelle
Was lässt Sie denken, dass Ihre binäre Suche nicht vorzeitig beendet werden kann? Sicherlich können Sie die Elemente einfach bei i und i + 1 testen, um festzustellen, ob sie den Zielwert einschließen, und beenden, wenn dies der Fall ist?
Paul R
Alternativ könnte ich die Elemente bei i und i-1 testen, um festzustellen, ob sie den Zielwert einschließen. Ich müsste auch testen, ob 'i'> = array.size () - 1 ist, damit ich Ihren Test vermeiden kann, und ob es <= 0 ist, damit ich meinen Test vermeiden kann ... es ist tatsächlich eine Menge Zusätzliche Bedingungen, die bei jedem Schritt ausgeführt werden müssen, um nach einem frühen Ausfall zu suchen. Ich kann mir vorstellen, dass sie den Algorithmus sehr verlangsamen würden, obwohl ich zugeben werde, dass ich das noch nicht wirklich profiliert habe.
Trevor Powell
3
Es muss nicht so kompliziert sein - wenn Ihr Array die Größe N hat, müssen Sie es nur so behandeln, als ob es die Größe N - 1 hätte. Auf diese Weise gibt es bei i + 1 immer ein gültiges Element. Sie tun a binäre Suche über N - 1 Element nach Element i, das kleiner als Ihr Zielwert ist, wobei Element i + 1 größer als der Zielwert ist.
Paul R

Antworten:

11

Der Code in der Frage (eine lineare Suche) wird, wie Sie zu Recht betonen, für große Float-Arrays langsam. Technisch gesehen ist es O (n), wobei n die Anzahl der Gleitkommawerte in Ihrem Array ist.

Im Allgemeinen ist das Beste, was Sie tun können, um einen Wert in einem geordneten Array zu finden, eine rekursive Baumsuche (z. B. eine binäre Suche). In diesem Fall können Sie eine O (log n) -Suchzeit für die Anzahl der Elemente erzielen in Ihrem Array. O (log n) ist für große Werte von n viel besser als O (n).

Mein vorgeschlagener Ansatz wäre daher eine einfache binäre Suche des Arrays , dh:

  1. Stellen Sie Min / Max-Ganzzahlindizes so ein, dass sie Ihr gesamtes Float-Array abdecken
  2. Testen Sie den Wert in der Mitte des Bereichs bei Index mid = (min + max / 2) gegen den Suchwert x
  3. Wenn x niedriger als dieser Wert ist, setzen Sie max auf mid, andernfalls min auf mid
  4. Wiederholen Sie (2-4), bis Sie den richtigen Wert gefunden haben

Dies ist ein O (log n) -Algorithmus, der für fast alle Situationen schnell genug sein sollte. Intuitiv halbiert es den zu durchsuchenden Bereich bei jedem Schritt, bis Sie den richtigen Wert gefunden haben.

Es ist wirklich schwer, die einfache binäre Suche zu testen. Wenn Sie dies also bereits korrekt implementiert haben, sind Sie möglicherweise bereits ziemlich nahe am Optimum. Wenn Sie jedoch die Verteilung der Daten kennen und / oder einen begrenzten Bereich von Suchwerten (x) haben, können Sie noch einige weitere fortgeschrittene Tricks ausprobieren:

  • Bucketing - Erstellen Sie Buckets (z. B. für jedes Intervall zwischen zwei Ganzzahlen), von denen jedes eine kleinere sortierte Liste der Gleitkommawerte zwischen den beiden begrenzenden Ganzzahlen sowie zwei Werte unmittelbar unter und unmittelbar über jedem Bereich enthält. Sie können dann Ihre Suche bei (abgeschnitten (x) +0,5) starten. Dies sollte Ihnen eine gute Beschleunigung bieten, wenn Sie Eimer mit geeigneter Größe auswählen (dies erhöht effektiv den Verzweigungsfaktor des Baums .....). Wenn Ganzzahlen für Sie nicht funktionieren, können Sie Buckets mit einer anderen Fixpunktgenauigkeit ausprobieren (z. B. Vielfache von 1/16).
  • Bit-Mapping - Wenn der Bereich möglicher Nachschlagewerte klein genug ist, können Sie versuchen, eine große Nachschlagetabelle zu erstellen, die durch den bitweisen Wert von x indiziert ist. Dies ist O (1), aber Sie benötigen möglicherweise viel Speicher, der in Ihrem Cache sehr unfreundlich ist. Verwenden Sie ihn daher mit Vorsicht. Dies ist besonders unangenehm, da Sie nach Float-Werten suchen. Daher benötigen Sie möglicherweise mehrere GB, um alle weniger wichtigen Bits zu berücksichtigen.
  • Rundung und Hashing - Hash-Tabellen sind wahrscheinlich nicht die beste Datenstruktur für dieses Problem. Wenn Sie jedoch überleben können, wenn Sie ein wenig an Genauigkeit verlieren, können sie funktionieren. Runden Sie einfach die niedrigsten Bits Ihrer Suchwerte ab und verwenden Sie eine Hashmap, um die direkt nachzuschlagen korrekter Wert. Sie müssen den richtigen Kompromiss zwischen Hashmap-Größe und Präzision ausprobieren und sicherstellen, dass alle möglichen Hash-Werte ausgefüllt sind, damit dies etwas schwierig sein kann.
  • Baumausgleich - Ihr idealer Baum sollte eine 50% ige Chance haben, nach links oder rechts zu gehen. Wenn Sie also einen Baum basierend auf der Verteilung der Suchwerte (x) erstellen, können Sie den Baum optimieren, um Antworten mit minimaler Anzahl von Tests zu erhalten. Dies ist wahrscheinlich eine gute Lösung, wenn viele Werte in Ihrem Float-Array sehr nahe beieinander liegen, da Sie so vermeiden können, diese Zweige zu oft zu durchsuchen.
  • Crit-Bit-Bäume - Dies sind immer noch Bäume (also immer noch O (log n) ...), aber einige Fälle: Sie müssten jedoch Ihre Floats in ein Festkomma-Format konvertieren, damit die Vergleiche funktionieren

Wenn Sie sich jedoch nicht in einer ganz besonderen Situation befinden, würde ich wahrscheinlich empfehlen, bei der einfachen binären Suche zu bleiben. Gründe dafür:

  • es ist viel einfacher zu implementieren
  • In den meisten Fällen ist es sehr schnell
  • Der zusätzliche Aufwand der komplexeren Ansätze (z. B. höhere Speichernutzung / Cache-Druck) überwiegt häufig die geringfügigen theoretischen Gewinne
  • Es wird robuster gegenüber zukünftigen Änderungen in der Datenverteilung sein.
mikera
quelle
1

Das scheint einfach zu sein:

Führen Sie eine binäre Suche nach dem Float durch, den Sie binden möchten - O (log n) time.

Dann ist das Element links davon die Untergrenze und das Element rechts davon die Obergrenze.

Ankit Soni
quelle
0

Die offensichtliche Antwort ist, die Schwimmer in einem Baum zu speichern . Die Unterstützung von "vorherigen" und "nächsten" Operationen ist in einem Baum trivial. Machen Sie einfach ein "Weiter" für Ihren Wert und dann ein "Zurück" für den Wert, den Sie im ersten Schritt finden.

David Schwartz
quelle
1
Dies entspricht im Wesentlichen einer binären Suche.
Kevin Cline
-1

Dieses Papier ("sublogarithmische Suche ohne Multiplikationen") könnte von Interesse sein; Es enthält sogar Quellcode. Zu Vergleichszwecken können Sie eine Gleitkommazahl als Ganzzahl mit demselben Bitmuster behandeln. Dies war eines der Entwurfsziele des IEEE-Gleitkomma-Standards.

zvrba
quelle