ソートされた浮動小数点数の配列を検索して、入力値を囲んでいる浮動小数点数のペアを見つける高速アルゴリズム


10

フロートの配列があり、最小から最大にソートされており、渡された入力値よりも大きいまたは小さい最も近いフロートを選択できる必要があります。この入力値は、必ずしも配列内の値として存在するわけではありません。

素朴なアプローチは、配列全体で単純な線形検索を行うことです。次のようになります。

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

しかし、明らかにアレイが大きくなるにつれて、これはますます遅くなります。

このデータをより最適に見つけることができるアルゴリズムについて誰かが考えていますか?私はすでにバイナリ検索に切り替えましたが、問題が多少改善されましたが、それでもまだ思ったよりもずっと遅く、配列に存在する特定の値を実際に探しているわけではないため、終了できません早い。

詳細:配列内の浮動小数点値は必ずしも均等に分散されているわけではありません(つまり、配列は値「1.f、2.f、3.f、4.f、100.f、1200.fで構成されている可能性があります」 、1203.f、1400.f "。

私はこの操作を数十万回実行していますが、ルックアップ時間を改善する場合は、フロートの配列に対して任意の量の前処理を実行できます。それを助けるために、私はそれらを保存するためにベクトル以外のものを使うように絶対に変えることができます。


バイナリ検索を早期に終了できないと思うのはなぜですか。確かに、iとi + 1の要素をテストして、それらがターゲット値を括弧で囲んでいるかどうかを確認し、そうである場合は終了できますか?
Paul R

あるいは、iとi-1の要素をテストして、それらがターゲット値を囲んでいるかどうかを確認することもできます。また、 'i'が> = array.size()-1であったかどうかをテストして、テストを回避できるようにしたり、<= 0であったかどうかをテストして、テストを回避したりできるようにしたりする必要があります。アーリーアウトをチェックするために、各ステップで実行する追加の条件文。私は実際にはまだプロファイリングしていないことを告白しますが、アルゴリズムが大幅に遅くなると思います。
Trevor Powell

3
それほど複雑である必要はありません。配列のサイズがNの場合は、N-1のサイズであるかのように処理する必要があります。これにより、常にi + 1に有効な要素があります。ターゲット値よりも小さい要素iのN-1要素のバイナリ検索。要素i + 1はターゲット値よりも大きい。
ポールR

回答:


11

質問(線形検索)のコードは、あなたが正しく指摘しているように、大きなfloat配列では遅くなるでしょう。技術的にはO(n)です。nは配列内のfloat値の数です。

一般に、順序付けされた配列で値を見つけるためにできる最善の方法は、ある種の再帰的なツリー検索(たとえば、バイナリ検索)です。この場合、要素数でO(log n)ルックアップ時間を達成できます。あなたの配列で。O(log n)は、nの値が大きい場合、O(n)よりもはるかに優れています。

したがって、私の提案するアプローチは、配列の単純なバイナリ検索です。つまり、

  1. フロート配列全体をカバーするように最小/最大整数インデックスを設定します
  2. インデックスmid =(min + max / 2)の範囲の中央の値を検索値xに対してテストします
  3. xがこの値よりも小さい場合は、maxをmidに設定し、それ以外の場合はminをmidに設定します
  4. 正しい値が見つかるまで(2-4)を繰り返します

これはO(log n)アルゴリズムであり、ほぼすべての状況で十分高速である必要があります。直感的には、正しい値が見つかるまで、各ステップで検索する範囲を半分にすることで機能します。

単純な二分探索を猛威を振るうのは本当に難しいので、すでにこれを正しく実装していれば、すでに最適にかなり近いかもしれません。ただし、データの分布がわかっていたり、ルックアップ値(x)の範囲が制限されていたりする場合でも、他にも高度なトリックを試すことができます。

  • バケットは -すぐ下の2つの境界の整数プラス2つの値の間の浮動小数点値の小さいソートされたリストが含まれており、すぐに各範囲より各々が、(二つの整数間の各間隔の例)バケットを作成します。その後、(trunc(x)+0.5)から検索を開始できます。これにより、適切なサイズのバケットを選択した場合、速度が向上します(ツリーの分岐係数が効果的に増加します...)。整数がうまくいかない場合は、他のいくつかの固定小数点精度(1/16の倍数など)のバケットを試すことができます。
  • ビットマッピング -可能なルックアップ値の範囲が十分に小さい場合、xのビットごとの値によってインデックスが付けられた大きなルックアップテーブルを作成してみることができます。これはO(1)になりますが、キャッシュ上で非常に不便になる大量のメモリが必要になる可能性があります。そのため、注意して使用してください。float値を検索しているため、これは特に厄介です。重要度の低いビットをすべて考慮するには、数GBが必要になる場合があります。
  • 丸めとハッシュ -ハッシュテーブルはおそらくこの問題に最適なデータ構造ではありませんが、多少の精度を失っても生き残ることができる場合、それらは機能します-ルックアップ値の最下位ビットを丸め、ハッシュマップを使用して直接ルックアップします正しい値。ハッシュマップのサイズと精度の間の適切なトレードオフを実験する必要があり、すべての可能なハッシュ値が入力されていることを確認する必要があるため、これは少しトリッキーになる可能性があります......
  • ツリーバランシング -理想的なツリーは、50%の確率で左または右に進みます。したがって、ルックアップ値の分布(x)に基づいてツリーを作成すると、ツリーを最適化して、最小限のテストで回答を生成できます。これは、float配列の多くの値が非常に接近している場合に適切なソリューションになる可能性があります。これにより、これらのブランチを頻繁に検索することを回避できるためです。
  • クリティカルビットツリー -これらはまだツリーです(したがって、まだO(log n)...)ですが、場合によっては、比較を機能させるために、浮動小数点数をいくつかの固定小数点形式に変換する必要があります

ただし、特別な状況でない限り、単純なバイナリ検索を使用することをお勧めします。理由:

  • 実装がはるかに簡単です
  • ほとんどの一般的なケースでは非常に高速です
  • より複雑なアプローチの余分なオーバーヘッド(たとえば、より高いメモリ使用量/キャッシュ圧力)は、マイナーな理論上の利益を上回ることがよくあります
  • データ分布の将来の変更に対してより堅牢になります。

1

これは十分に簡単に思えます:

バインドするフロートのバイナリ検索を実行します-O(log n)時間。

次に、その左側の要素が下限であり、その右側の要素が上限です。


0

明白な答えは、フロートをツリーに格納することです。「前の」および「次の」操作のサポートは、ツリーでは簡単です。したがって、値に対して「次へ」を実行し、最初のステップで見つけた値に対して「前へ」を実行するだけです。


1
これは基本的にバイナリ検索と同じです。
ケビンクライン14

-1

この論文(「乗算を伴わないblogarithmic search」)は興味深いかもしれません。ソースコードも含まれています。比較のために、浮動小数点数を同じビットパターンの整数として扱うことができます。これは、IEEE浮動小数点標準の設計目標の1つでした。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.