web-dev-qa-db-ja.com

バイナリ検索で複数のエントリを見つける

標準のbinary searchを使用してsorted listで単一のオブジェクトをすばやく返します(ソート可能なプロパティに関して)。

[〜#〜] all [〜#〜]一致するリストエントリが返されるように、検索を変更する必要があります。これを行うにはどうすればよいですか?

22
Gruber

さて、リストがソートされているので、関心のあるすべてのエントリはcontiguousです。これは、バイナリサーチによって生成されたインデックスから逆に見て、見つかったアイテムと等しい最初のアイテムを見つける必要があることを意味します。そして最後のアイテムについても同じです。

見つかったインデックスから単純に逆方向に進むことができますが、この方法では、見つかったものと等しいアイテムが多数ある場合、ソリューションはO(n)と同じくらい遅くなる可能性があります。 exponential searchを使用:より多くの等しいアイテムを見つけると、ジャンプが2倍になります。

23
Vlad

まず、単純なバイナリ検索コードスニペットを思い出してみましょう。

int bin_search(int arr[], int key, int low, int high)
{
    if (low > high)
        return -1;

    int mid = low + ((high - low) >> 1);

    if (arr[mid] == key) return mid;
    if (arr[mid] > key)
        return bin_search(arr, key, low, mid - 1);
    else
        return bin_search(arr, key, mid + 1, high);
}

スキーナ教授からの引用:(s [middle] == key)return(middle);の場合、等価テストを削除するとします。上記の実装から、検索が失敗するたびに-1の代わりに低いインデックスを返します。等価性テストがないため、すべての検索が失敗します。検索は、キーが同一の配列要素と比較されるたびに右半分に進み、最終的に右の境界で終了します。バイナリ比較の方向を逆にして検索を繰り返すと、左側の境界に移動します。各検索にはO(lgn)時間がかかるため、ブロックのサイズに関係なく、発生を対数時間でカウントできます。

したがって、lower_bound(KEY以上の最初の数値を見つける)とupper_bound(KEYより大きい最初の数値を見つける)を見つけるには、2ラウンドのbinary_searchが必要です。

int lower_bound(int arr[], int key, int low, int high)
{
    if (low > high)
        //return -1;
        return low;

    int mid = low + ((high - low) >> 1);
    //if (arr[mid] == key) return mid;

    //Attention here, we go left for lower_bound when meeting equal values
    if (arr[mid] >= key) 
        return lower_bound(arr, key, low, mid - 1);
    else
        return lower_bound(arr, key, mid + 1, high);
}

int upper_bound(int arr[], int key, int low, int high)
{
    if (low > high)
        //return -1;
        return low;

    int mid = low + ((high - low) >> 1);
    //if (arr[mid] == key) return mid;

    //Attention here, we go right for upper_bound when meeting equal values
    if (arr[mid] > key) 
        return upper_bound(arr, key, low, mid - 1);
    else
        return upper_bound(arr, key, mid + 1, high);
}

それが役に立てば幸い:)

16
user2696499

私があなたの質問に従っている場合、比較のために{1,2,2,3,4,5,5,5,6,7,8,8,9}のように見えるオブジェクトのリストがあります。 5の通常の検索は、5と比較するオブジェクトの1つにヒットしますが、それらすべてを取得したいのですが、そうですか?

その場合は、標準のバイナリ検索をお勧めします。これは、一致する要素に着地すると、一致が停止するまで左に検索を開始し、一致が停止するまで再び(最初の一致から)右に検索します。

使用しているデータ構造が同じものと比較する要素を上書きしていないことに注意してください!

または、その位置にあるバケットと同じ要素を格納する構造の使用を検討してください。

7
Miquel

Bsearchとの一致を見つけたら、一致がなくなるまで両側を再帰的にbsearchします

疑似コード:

    range search (type *array) {
      int index = bsearch(array, 0, array.length-1);

      // left
      int upperBound = index -1;
      int i = upperBound;
      do {
         upperBound = i;
         i = bsearch(array, 0, upperBound);
      } while (i != -1)

      // right
      int lowerBound = index + 1;
      int i = lowerBound;
      do {
         lowerBound = i;
         i = bsearch(array, lowerBound, array.length);
      } while (i != -1)

      return range(lowerBound, UpperBound);
}

コーナーケースはカバーされていません。私はこれがあなたの複雑さを(O(logN))に保つと思います。

3
yngccc

私は2つのバイナリ検索を実行します。1つ目は、値を比較する最初の要素(C++用語では、lower_bound)を探し、次に1つ目の要素を検索する>値を比較します(C++用語では、upper_bound)。 lower_boundから上限の直前までの要素が探しているものです(Java.util.SortedSet、subset(key、key)に関して)。

したがって、標準のバイナリ検索に2つの異なる修正を加える必要があります。プローブを行い、プローブでの比較を使用して、探している値が存在しなければならない領域を絞り込みます。 lower_boundの場合、等しい場合、探している要素(first等しい値)は、これまでの範囲の最初の要素とプローブした値の間のどこかにあることがわかります-すぐに戻ることはできません。

3
mcdowella

これは、使用するバイナリ検索の実装によって異なります。

  • Javaおよび.NETでは、バイナリ検索により任意の要素が得られます。探している範囲を取得するには、両方の方法で検索する必要があります。
  • C++では equal_range メソッドを使用して、1回の呼び出しで必要な結果を生成します。

Javaおよび.NETでの検索を高速化するために、等しい範囲が長すぎて線形反復できない場合は、先行要素と後続要素を検索し、中央で値を取ることができます。あなたが見つけた範囲の、端を除いて。

2番目のバイナリ検索のためにこれが遅すぎる場合は、両端を同時に検索する独自の検索を作成することを検討してください。これは少し面倒かもしれませんが、より高速に実行されるはずです。

2
dasblinkenlight

(「通常の」バイナリ検索を使用して)並べ替え可能なプロパティが指定された単一の要素のインデックスを検索することから始め、次にリスト内の要素の左右の両方を探し始め、検索に一致するすべての要素を追加します基準、要素が基準を満たさない場合、またはトラバースする要素がなくなった場合に一端で停止し、左右の両端が前述の停止条件を満たした場合に完全に停止します。

2
Óscar López

バイナリサーチは要素を返しますか、それとも要素のインデックスを返しますか?インデックスを取得できますか?

リストはソートされているため、一致するすべての要素が隣接して表示されます。標準検索で返されたアイテムのインデックスを取得できる場合は、一致しないものが見つかるまで、そのインデックスから双方向で検索する必要があります。

1
Colin D

これを試して。それは驚くほどうまくいきます。

実際の例 ここをクリック

   var arr = [1, 1, 2, 3, "a", "a", "a", "b", "c"]; // It should be sorted array.
   // if it arr contain more than one keys than it will return an array indexes. 

   binarySearch(arr, "a", false);

   function binarySearch(array, key, caseInsensitive) {
       var keyArr = [];
       var len = array.length;
       var ub = (len - 1);
       var p = 0;
       var mid = 0;
       var lb = p;

       key = caseInsensitive && key && typeof key == "string" ? key.toLowerCase() : key;

       function isCaseInsensitive(caseInsensitive, element) {
           return caseInsensitive && element && typeof element == "string" ? element.toLowerCase() : element;
       }
       while (lb <= ub) {
           mid = parseInt(lb + (ub - lb) / 2, 10);

           if (key === isCaseInsensitive(caseInsensitive, array[mid])) {
               keyArr.Push(mid);
               if (keyArr.length > len) {
                   return keyArr;
               } else if (key == isCaseInsensitive(caseInsensitive, array[mid + 1])) {
                   for (var i = 1; i < len; i++) {
                       if (key != isCaseInsensitive(caseInsensitive, array[mid + i])) {
                           break;
                       } else {
                           keyArr.Push(mid + i);

                       }
                   }
               }
               if (keyArr.length > len) {
                   return keyArr;
               } else if (key == isCaseInsensitive(caseInsensitive, array[mid - 1])) {
                   for (var i = 1; i < len; i++) {

                       if (key != isCaseInsensitive(caseInsensitive, array[mid - i])) {
                           break;
                       } else {
                           keyArr.Push(mid - i);
                       }
                   }
               }
               return keyArr;

           } else if (key > isCaseInsensitive(caseInsensitive, array[mid])) {
               lb = mid + 1;
           } else {
               ub = mid - 1;
           }
       }

       return -1;
   }
0
shekhardtu

あなたはあなたの問題のために以下のコードを使うことができます。ここでの主な目的は、最初にキーの下限を見つけ、次に同じ上限を見つけることです。後でインデックスの違いがわかり、答えがわかります。 2つの異なる関数を使用するのではなく、同じ関数の上限と下限を見つけるために使用できるフラグを使用できます。

#include <iostream>
#include <bits/stdc++.h>
using namespace std;

int bin_search(int a[], int low, int high, int key, bool flag){
long long int mid,result=-1;
while(low<=high){
    mid = (low+high)/2;
    if(a[mid]<key)
        low = mid + 1;
    else if(a[mid]>key)
        high = mid - 1;
    else{
        result = mid;
        if(flag)
            high=mid-1;//Go on searching towards left (lower indices)
        else
            low=mid+1;//Go on searching towards right (higher indices)
    }
}
return result;
}

int main() {

int n,k,ctr,lowind,highind;
cin>>n>>k;
//k being the required number to find for
int a[n];
for(i=0;i<n;i++){
    cin>>a[i];
}
    sort(a,a+n);
    lowind = bin_search(a,0,n-1,k,true);
    if(lowind==-1)
        ctr=0;
    else{
        highind = bin_search(a,0,n-1,k,false);
        ctr= highind - lowind +1;   
}
cout<<ctr<<endl;
return 0;
}
0
Deril Raju