web-dev-qa-db-ja.com

ソートされた配列で、ターゲットより大きい最初の要素を見つけます

一般的なバイナリ検索では、配列に表示される値を探しています。ただし、ターゲットよりも大きいまたは小さい最初の要素を見つける必要がある場合があります。

ここに私のmyい、不完全なソリューションがあります:

// Assume all elements are positive, i.e., greater than zero
int bs (int[] a, int t) {
  int s = 0, e = a.length;
  int firstlarge = 1 << 30;
  int firstlargeindex = -1;
  while (s < e) {
    int m = (s + e) / 2;
    if (a[m] > t) {
      // how can I know a[m] is the first larger than
      if(a[m] < firstlarge) {
        firstlarge = a[m];
        firstlargeindex = m;
      }
      e = m - 1; 
    } else if (a[m] < /* something */) {
      // go to the right part
      // how can i know is the first less than  
    }
  }
}

この種の問題に対してよりエレガントな解決策はありますか?

50
SecureFish

この問題について特にエレガントに考える方法は、関数の適用によって配列が変更された配列の変換されたバージョンに対してバイナリ検索を行うことを考えることです。

f(x) = 1 if x > target
       0 else

ここでの目標は、この関数が値1をとる最初の場所を見つけることです。次のようにバイナリ検索を使用してそれを行うことができます。

int low = 0, high = numElems; // numElems is the size of the array i.e arr.size() 
while (low != high) {
    int mid = (low + high) / 2; // Or a fancy way to avoid int overflow
    if (arr[mid] <= target) {
        /* This index, and everything below it, must not be the first element
         * greater than what we're looking for because this element is no greater
         * than the element.
         */
        low = mid + 1;
    }
    else {
        /* This element is at least as large as the element, so anything after it can't
         * be the first element that's at least as large.
         */
        high = mid;
    }
}
/* Now, low and high both point to the element in question. */

このアルゴリズムが正しいことを確認するには、各比較を検討してください。ターゲット要素よりも大きくない要素が見つかった場合、その要素とその下にあるすべてのものは一致しない可能性があるため、その領域を検索する必要はありません。右半分を再帰的に検索できます。問題の要素よりも大きい要素が見つかった場合、それ以降の要素も大きくなければならないため、より大きなfirst要素にはなれないため、検索する必要はありません。それら。したがって、中央の要素は、最後の可能な場所です。

各反復で、考慮対象から残りの要素の少なくとも半分を削除することに注意してください。上のブランチが実行されると、[low、(low + high)/ 2]の範囲の要素はすべて破棄され、floor((low + high)/ 2)-low + 1> =(low +高)/ 2-低=(高-低)/ 2要素。

下のブランチが実行されると、[(low + high)/ 2 + 1、high]の範囲の要素はすべて破棄されます。これにより、高さが失われます-floor(low + high)/ 2 + 1> = high-(low + high)/ 2 =(high-low)/ 2要素。

その結果、このプロセスのO(lg n)回の反復で、ターゲットより大きい最初の要素を見つけることになります。

編集:配列0 0 1 1 1 1で実行されているアルゴリズムのトレースです。

最初は

0 0 1 1 1 1
L = 0       H = 6

したがって、mid =(0 + 6)/ 2 = 3を計算します。したがって、値1を持つ位置3の要素を検査します。1> 0なので、high = mid = 3に設定します。

0 0 1
L     H

Mid =(0 + 3)/ 2 = 1を計算するので、要素1を検査します。これは値0 <= 0であるため、mid = low + 1 = 2に設定します。 = 3:

0 0 1
    L H

ここで、mid =(2 + 3)/ 2 = 2を計算します。インデックス2の要素は1です。1≥0なので、H = mid = 2に設定し、この時点で停止し、実際に探しています。 0より大きい最初の要素。

お役に立てれば!

76
templatetypedef

配列がソートされている場合は、std::upper_boundを使用できます(nが配列a[]のサイズであると仮定):

int* p = std::upper_bound( a, a + n, x );
if( p == a + n )
     std::cout << "No element greater";
else
     std::cout << "The first element greater is " << *p
               << " at position " << p - a;
9

次の再帰的なアプローチはどうですか:

    public static int minElementGreaterThanOrEqualToKey(int A[], int key,
        int imin, int imax) {

    // Return -1 if the maximum value is less than the minimum or if the key
    // is great than the maximum
    if (imax < imin || key > A[imax])
        return -1;

    // Return the first element of the array if that element is greater than
    // or equal to the key.
    if (key < A[imin])
        return imin;

    // When the minimum and maximum values become equal, we have located the element. 
    if (imax == imin)
        return imax;

    else {
        // calculate midpoint to cut set in half, avoiding integer overflow
        int imid = imin + ((imax - imin) / 2);

        // if key is in upper subset, then recursively search in that subset
        if (A[imid] < key)
            return minElementGreaterThanOrEqualToKey(A, key, imid + 1, imax);

        // if key is in lower subset, then recursively search in that subset
        else
            return minElementGreaterThanOrEqualToKey(A, key, imin, imid);
    }
}
2

長年アルゴリズムを教えてきた後、バイナリ検索の問題を解決するための私のアプローチは、配列の外側ではなく要素で開始と終了を設定することです。このようにして、ソリューションについて魔法を感じることなく、何が起こっているのか、すべてが制御されているのを感じることができます。

バイナリ検索問題(および他の多くのループベースのソリューション)を解決する際の重要なポイントは、適切な不変式のセットです。適切な不変式を選択すると、問題が解決されます。不変の概念を理解するのに何年もかかりましたが、何年も前に大学で最初にそれを学びました。

配列の外側で開始または終了を選択してバイナリ検索の問題を解決したい場合でも、適切な不変式でそれを達成できます。そうは言っても、上で述べたように、常に配列の最初の要素で開始し、最後の要素で終了するように選択します。

要約すると、これまでのところ次のとおりです。

int start = 0; 
int end = a.length - 1; 

今不変です。現在の配列は[start、end]です。要素についてはまだ何も知りません。それらのすべてがターゲットよりも大きいか、すべてがより小さいか、またはいくつかの小さいものと大きいものがあります。そのため、これまでのところ、要素について仮定を立てることはできません。私たちの目標は、ターゲットより大きい最初の要素を見つけることです。したがって、次のようにinvariantsを選択します。

終了の右にある要素は、ターゲットよりも大きくなっています。
開始点の左にある要素は、ターゲット以下です。

不変式が開始時(つまり、ループに入る前)に正しいことが簡単にわかります。開始点の左にあるすべての要素(基本的に要素はありません)は、ターゲットと同じかそれ以下です。終了の理由は同じです。

この不変式では、ループが終了すると、終了後の最初の要素が答えになります(終了の右側がすべてターゲットよりも大きいという不変式を覚えていますか?)。そう answer = end + 1

また、ループが終了すると、startはendよりも1つ多くなることに注意する必要があります。つまり、start = end + 1です。したがって、同様にstartが答えであると言えます(不変は、startの左にあるものがターゲットより小さいか等しいため、start自体がターゲットより大きい最初の要素です)。

すべてが言われているので、ここにコードがあります。このコードのすべての行について安心し、魔法をまったく感じないはずです。そうでない場合は、あいまいさをコメントしてください。私は喜んで答えます。

public static int find(int a[], int target) {
    int st = 0; 
    int end = a.length - 1; 
    while(st <= end) {
        int mid = (st + end) / 2;   // or elegant way of st + (end - st) / 2; 
        if (a[mid] <= target) {
            st = mid + 1; 
        } else { // mid > target
            end = mid - 1; 
        }
    }
    return st; // or return end + 1
}

バイナリ検索問題を解決するこの方法に関するいくつかの追加の注意事項:

このタイプのソリューションは、常にサブアレイのサイズを少なくとも1縮小します。これは、コードから明らかです。新しい開始または終了は、中央で+1または-1です。私はこのアプローチが両方または片側にミッドを含めるよりも優れているので、後でアルゴリズムが正しい理由を説明します。この方法により、より具体的でエラーがより少なくなります。

Whileループの条件はst <= endst < end。これは、whileループに入る最小サイズがサイズ1の配列であることを意味します。これは、予想どおりに完全に一致します。バイナリ検索問題を解決する他の方法では、最小サイズがサイズ2の配列(st <endの場合)である場合があり、正直なところ、サイズ1を含むすべての配列サイズを常に処理する方がはるかに簡単です。

したがって、この問題と他の多くのバイナリ検索問題の解決策が明らかになることを願っています。この解決策は、アルゴリズムがEdgeのケースで機能するかどうかをぐらつかずに、より多くのバイナリ検索問題を専門的に理解および解決する方法として扱います。

2
apadana

ここは修正された バイナリ検索 コードイン Java 時間の複雑さ O(logn) それ :

  • 返す インデックス 検索する要素の 要素が存在する場合
  • 検索された要素が配列に存在しない場合、次に大きい要素のインデックスを返します
  • 配列の最大要素より大きい要素が検索された場合、-1を返します
public static int search(int arr[],int key) {
    int low=0,high=arr.length,mid=-1;
    boolean flag=false;

    while(low<high) {
        mid=(low+high)/2;
        if(arr[mid]==key) {
            flag=true;
            break;
        } else if(arr[mid]<key) {
            low=mid+1;
        } else {
            high=mid;
        }
    }
    if(flag) {
        return mid;
    }
    else {
        if(low>=arr.length)
            return -1;
        else
        return low;
        //high will give next smaller
    }
}

public static void main(String args[]) throws IOException {
    BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
    //int n=Integer.parseInt(br.readLine());
    int arr[]={12,15,54,221,712};
    int key=71;
    System.out.println(search(arr,key));
    br.close();
}
1
pankaj

次の実装では、@ templatetypedefの答えとは異なる条件bottom <= topを使用します。

int FirstElementGreaterThan(int n, const vector<int>& values) {
  int B = 0, T = values.size() - 1, M = 0;
  while (B <= T) { // B strictly increases, T strictly decreases
    M = B + (T - B) / 2;
    if (values[M] <= n) { // all values at or before M are not the target
      B = M + 1;
    } else {
      T = M - 1;// search for other elements before M
    }
  }
  return T + 1;
}
1
Duke

kind = 0:完全一致、kind = 1:xより大きい、kind-1:xより小さい;

一致が見つからない場合は-1を返します。

#include <iostream>
#include <algorithm>

using namespace std;


int g(int arr[], int l , int r, int x, int kind){
    switch(kind){
    case 0: // for exact match
        if(arr[l] == x) return l;
        else if(arr[r] == x) return r;
        else return -1;
        break;
    case 1: // for just greater than x
        if(arr[l]>=x) return l;
        else if(arr[r]>=x) return r;
        else return -1;
        break;
    case -1: // for just smaller than x
        if(arr[r]<=x) return r;
        else if(arr[l] <= x) return l;
        else return -1;
        break;
    default:
        cout <<"please give "kind" as 0, -1, 1 only" << ednl;
    }
}

int f(int arr[], int n, int l, int r, int x, int kind){
    if(l==r) return l;
    if(l>r) return -1;
    int m = l+(r-l)/2;
    while(m>l){
        if(arr[m] == x) return m;
        if(arr[m] > x) r = m;
        if(arr[m] < x) l = m;
        m = l+(r-l)/2;
    }
    int pos = g(arr, l, r, x, kind);
    return pos;
}

int main()
{
    int arr[] = {1,2,3,5,8,14, 22, 44, 55};
    int n = sizeof(arr)/sizeof(arr[0]);
    sort(arr, arr+n);
    int tcs;
    cin >> tcs;
    while(tcs--){
        int l = 0, r = n-1, x = 88, kind = -1; // you can modify these values
        cin >> x;
        int pos = f(arr, n, l, r, x, kind);
        // kind =0: exact match, kind=1: just grater than x, kind=-1: just smaller than x;
        cout <<"position"<< pos << " Value ";
        if(pos >= 0) cout << arr[pos];
        cout << endl;
    }
    return 0;
}
0