web-dev-qa-db-ja.com

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

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

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

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 "。

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

10
Trevor Powell

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

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

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

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

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

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

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

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

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

これは十分に簡単なようです:

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

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

1
Ankit Soni

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

0
David Schwartz