web-dev-qa-db-ja.com

順序付きリストのバイナリ検索よりも高速

配列のソートされた値を検索するために、バイナリ検索よりも高速なアルゴリズムはありますか?

私の場合、A配列にソートされた値(任意の型の値)があります。探していた値がA[n] and A[n+1]の範囲内にある場合は、nを返す必要があります。

29
uray

値が整数の場合、O(log n)よりもうまくいく可能性があります。その場合、nに関して達成できる最良の最悪の実行時間は、O(sqrt(log n))です。そうでない場合、入力シーケンスにパターンがない限り、O(log n)に勝つ方法はありません。整数の場合、O(log n)を打ち負かすために使用される2つのアプローチがあります。

まず、ハッシュテーブルに、その接頭辞を持つ少なくとも1つの整数を格納するすべての接頭辞を格納することで機能するy-fastツリーを使用できます。これにより、バイナリ検索を実行して、最長の一致するプレフィックスの長さを見つけることができます。これにより、時間内に検索している要素の後継要素O(log w)を見つけることができます。ここで、wはWordのビット数です。これを機能させ、線形空間のみを使用するために機能する詳細がいくつかありますが、それほど悪くはありません(以下のリンクを参照)。

第2に、ビットトリックを使用して一定数の命令でw ^ O(1)比較を実行できるフュージョンツリーを使用して、O(log n/log w)の実行時間を得ることができます。

これら2つのデータ構造間の最適なトレードオフは、log w = sqrt(log n)の場合に発生し、実行時間はO(sqrt(log n))になります。

上記の詳細については、Erik Demaineのコースの講義12および13を参照してください。 http://courses.csail.mit.edu/6.851/spring07/lec.html

37
jonderry

1つの可能性は、関数のルーツを見つけるように扱うことです。基本的に、以下を見つけます:

a[i] <= i <= a[i + 1]

以下と同等です。

a[i] - i <= 0 <= a[i + 1] - i

次に、ニュートンの方法などを試すことができます。これらの種類のアルゴリズムは、機能するときにバイナリ検索よりも速く収束することがよくありますが、すべての入力に対して収束することが保証されているアルゴリズムは知りません。

http://en.wikipedia.org/wiki/Root-finding_algorithm

6
xscott

リストの値が均等に分布している場合は、バイナリ分割ではなく、重み付け分割を試すことができます。目的の値が現在の下限から現在の値までの3分の1の距離にある場合は、3分の1の距離にある要素を試すことができます。ただし、値がまとめられているリストでは、これはひどく苦しむ可能性があります。

次のアルゴはどうですか?これは指数探索と呼ばれ、二分探索のバリエーションの1つです。 http://en.m.wikipedia.org/wiki/Exponential_search

サイズnのソートされた配列Aで要素kを検索しています。 Aのkの位置を超えるまで、i = 0、1、2、...のA [2 ^ i]を検索します。次に、配列の左側(iよりも小さい)の部分でバイナリ検索を実行します。

int exponential_search(int A[], int key)
{
  // lower and upper bound for binary search
  int lower_bound = 0;
  int upper_bound = 1;

  // calculate lower and upper bound
  while (A[upper_bound] < key) {
    lower_bound = upper_bound;
   upper_bound = upper_bound * 2;
  }
  return binary_search(A, key, lower_bound, upper_bound);
}

このアルゴリズムはO(log idx)で実行されます。ここで、idxはAのkのインデックスです(両方のstpesはlog idxにあります)。最悪の場合、kがAの最大の要素の中にあるか、Aのどの要素よりも大きい場合、アルゴはO(log idx)にあります。乗算定数は、バイナリ検索の場合よりも大きくなりますが、非常に大きい場合、アルゴはより速く実行されます配列と、配列の先頭に向かっているデータを探す場合。

このアルゴが二分探索よりも好ましい最小サイズnについて少し考えたいのですが、わかりません。

4
user2747438

はいといいえ。はい、二分検索よりも平均して高速な検索があります。しかし、それらはまだ低い定数で、まだO(lg N)であると私は信じています。

要素を見つけるのにかかる時間を最小限に抑えたいと考えています。一般に、使用するステップを少なくすることが望ましく、これにアプローチする1つの方法は、各ステップで削除される要素の予想数を最大化することです。二分法では、常に要素のちょうど半分が削除されます。要素の分布について何か知っていれば、これよりもうまくいくことができます。ただし、パーティション要素を選択するためのアルゴリズムは、通常、中間点を選択するよりも複雑であり、この余分な複雑さは、少ないステップを使用することで得られると予想される時間の節約を圧倒する可能性があります。

実際、このような問題では、検索アルゴリズムよりも、キャッシュの局所性などの2次効果を攻撃する方が適切です。たとえば、二分探索を繰り返す場合、同じ少数の要素(1番目、2番目、および3番目の四分位数)が非常に頻繁に使用されるため、それらを1つのキャッシュ行に配置する方が、リストへのランダムアクセスよりもはるかに優れている可能性があります。

各レベルを(2ではなく)4つまたは8つの等しいセクションに分割し、それらを線形検索することも、分割を計算する必要がなく、データの依存関係が少ないため、二分検索よりも高速になる可能性があります。キャッシュストールを引き起こします。

しかし、これらはすべてO(lg N)のままです。

4
Ben Voigt

まず、最適化を行う前に測定

その検索を本当に最適化する必要がありますか?

もしそうなら、次に、アルゴリズムの複雑さを最初に考えます。例えば。配列の代わりに(std::mapなどの)ツリーを使用できますか?その場合、挿入と削除の相対的な頻度と検索の頻度に依存しますが、手元に並べ替えられた配列があるという前提は、データセットの変更と比較して検索が頻繁であることを示しているため、挿入/削除。各検索がはるかに高速になります。つまり、対数時間です。

実際に検索時間がアドレッシングを必要とするボトルネックであり、データ表現の変更が不可能であり、リストが短い場合、線形検索は比較ごとの処理が少ないため、一般的に高速になります。

それ以外の場合、リストが長く、値の特定の分布が不明または想定されておらず、値を数値として扱うことができず、メモリ消費が一定である必要があります(たとえば、ハッシュテーブルの作成を除外する)。その後、バイナリ検索比較ごとに1ビットの情報を生成し、おそらく最初の検索で実行できる最善の方法です。

乾杯&hth。

一般的な場合、O(log N)よりも優れた方法はありませんが、少なくともそれを最適化できるため、O(log N)の前の比例定数を大幅に減らすことができます。

同じ配列で複数の検索を実行する必要がある場合、SIMD拡張を使用してこれらをベクトル化できるため、計算コストをさらに削減できます。

特に、特定のプロパティを満たす浮動小数点数の配列を処理している場合は、O(1)で配列を検索できる特別なインデックスを作成する方法があります。

Cannizzo、2015、バイナリ検索の高速かつベクトル化可能な代替手段O(1)ソート済みの広いドメインに適用可能浮動小数点数の配列 このペーパーには、 github のソースコードが付属しています。

1
Fabio

あなたはいつでもそれらをハッシュテーブルに置くことができ、それから検索はO(1)になります。ただし、メモリを集中的に使用するため、アイテムを追加し続けると、ハッシュテーブルを再バケットする必要がある場合があります。再バケット化はO(n)ですが、O(1)に償却されます。それは、そのスペースとキャッシュミスの可能性があるかどうかに本質的に依存します。

1
srean

膨大な数の数値を検索する必要があり、いくつかのまぐれによってそれらもソートされている場合は、O(n + m)で行うことができます。ここで、mは検索する数値の数です。基本的には、典型的なマージアルゴリズムだけですが、配列に挿入される場合、チェックされた各数値が以前に挿入される値を記録するようにわずかに変更されています。

あなたはいつでもスペースをトレードオフすることができます...そして他の操作の時間。すべての要素が一定サイズのpビットであると仮定すると、検索できる値ごとに、現在格納されている次に大きい値のインデックスを格納する大規模な配列を作成できます。この配列は2 ^ p * lg(n)ビットである必要があります。ここで、nは格納されている数値です。各挿入または削除はO(2 ^ p)ですが、これらすべてのインデックスを更新する必要があるため、通常は約2 ^ p/nです。

しかし、ルックアップはO(1)になりました!

OK、OK、それは実際には実用的ではありません。しかし、同様の方法で入力をブロックに分割すると、ログの前にある定数が減少する可能性があります。おそらく。

0
David

バイナリ検索では、リストを2つの「サブリスト」に分割し、値を含む可能性のあるサブリストのみを検索します。アレイの大きさにもよりますが、アレイを3つ以上のスプライスに分割すると、スピードアップが見られます。

インデックスを保持することにより、最初に検索する配列のどの領域を検索する必要があるかを判断できます。大都市の電話帳のように、外から見ることができ、検索を開始する必要があります。 (私は自分の考えをテキストで表現するのに苦労していて、それをよりよく説明する英語のリンクをまだ見つけられませんでした)。

0
bjoernz