web-dev-qa-db-ja.com

上限と下限の基本的なバイナリ検索の違いは?

記事 http://community.topcoder.com/tc?module=Static&d1=tutorials&d2=binarySearch で、著者はバイナリ検索について説明しています。彼は何かが真である最低値を見つけることと、何かが偽である最高値を見つけることを区別しています。検索される配列は次のようになります。

false false false true true

これら2つのケースが異なる理由について、私は興味があります。なぜ真である最低値を見つけてから1を引いて、偽である最高値を見つけられないのですか?

Edit2:わかりましたので、下限と上限を理解しました。今、私は理解するのに苦労しています、クエリ以上の最小の整数を検索するとき、if(mid>query)if(mid>=query)に変更してそれを低くすることができない理由上限の代わりに。

編集:これは記事が述べたことです:

「これでようやく、このセクションと前のセクションで説明したバイナリ検索を実装するコードに到達しました。

binary_search(lo, hi, p):
   while lo < hi:
      mid = lo + (hi-lo)/2
      if p(mid) == true:
         hi = mid
      else:
         lo = mid+1

   if p(lo) == false:
      complain                // p(x) is false for all x in S!

   return lo         // lo is the least x for which p(x) is true

...

p(x)がfalseである最後のxを見つけたい場合は、(上記と同様の根拠を使用して)次のように工夫します。

binary_search(lo, hi, p):
   while lo < hi:
      mid = lo + (hi-lo+1)/2    // note: division truncates
      if p(mid) == true:
         hi = mid-1
      else:
         lo = mid

   if p(lo) == true:
      complain                // p(x) is true for all x in S!

   return lo         // lo is the greatest x for which p(x) is false

13
John Targaryen

二分探索の下限と上限は、順序を壊さずに値を挿入できる最低位置と最高位置です。 (C++標準ライブラリでは、これらの境界は、値を挿入する前に要素を参照する反復子によって表されますが、概念は本質的に変更されていません。)

たとえば、並べ替えられた範囲を取る

1 2 3 4 5 5 5 6 7 9

3のバイナリ検索では、

   v-- lower bound
1 2 3 4 5 5 5 6 7 9
     ^-- upper bound

そして5のバイナリ検索では:

       v-- lower bound
1 2 3 4 5 5 5 6 7 9
             ^-- upper bound

要素が範囲内に存在しない場合、下限と上限は同じです。 8のバイナリ検索では:

                 v-- lower bound
1 2 3 4 5 5 5 6 7 9
                 ^-- upper bound

あなたが参照している記事の著者は、これらすべてを「より小」および「より大」という同等の用語で表現しているため、5つの検索で

       v-- lower bound
t t t t f f f f f f      <-- smaller than?
1 2 3 4 5 5 5 6 7 9
f f f f f f f t t t      <-- greater than?
             ^-- upper bound

C++イテレータは、これらすべての場合で、境界のすぐ後ろにある要素を参照します。つまり、

  • 3の検索では、std::lower_boundが返すイテレータは3を参照し、std::upper_boundからのイテレータは4を参照します
  • 5の検索では、std::lower_boundによって返されるイテレータは最初の5を参照し、std::upper_boundからのイテレータは6を参照します
  • 8の検索では、どちらも9を参照します

これは、挿入のためのC++標準ライブラリの規則が、新しい要素を挿入する前に要素を参照する反復子を渡すことであるためです。たとえば、後

std::vector<int> vec { 1, 3, 4, 5, 5, 5, 6, 7, 9 };
vec.insert(vec.begin() + 1, 2);

vecには1, 2, 3, 4, 5, 5, 5, 6, 7, 9が含まれます。 std::lower_boundおよびstd::upper_boundは、この規則に従って、

vec.insert(std::lower_bound(vec.begin(), vec.end(), 5), 5);
vec.insert(std::upper_bound(vec.begin(), vec.end(), 8), 8);

必要に応じて機能し、vecをソートしたままにします。

より一般的には、これはC++標準ライブラリで範囲を指定する方法を表しています。範囲の最初の反復子は、範囲の最初の要素(存在する場合)を参照し、終了反復子は、範囲の最後の直後の要素(存在する場合)を参照します。別の見方をすると、std::lower_boundおよびstd::upper_boundによって返されるイテレータは、検索された要素と同等の、検索された範囲内の要素の範囲にまたがっています。

要素が範囲内にない場合、この範囲は空なので、lower_boundupper_boundは同じイテレータを返し、それ以外の場合はlower_boundは検索範囲の最初の要素を参照するイテレータを返しますこれは検索値に相当しますが、upper_boundは、最後の要素のすぐ後ろにある要素(ある場合)を参照する反復子を返します。

40
Wintermute

配列が常に

false … true …

その場合、index 0でtrueを見つけない限り、見つける前のインデックスは常にfalseになります。上記の私のコメントで述べたように、もう1つの境界ケースは、trueが見つからない場合です。次に、最も高い偽が配列の最後の部分になります。

1
royhowie

2つのアルゴリズムは、コードスニペットから実際に明らかなように、trueまたはfalseの値がない場合に発生する状況の条件が明らかに異なります。値はtrueであり、この位置から1を引いてfalseを生成する最も高い値を見つけます。そのようなオブジェクトがないため、誤った結果が生成されます。アルゴリズムは、適切な要素を特定することを処理する異なる要素を直接対象とするだけなので、特別なケースを持つのではなく、特別なケースを処理する必要がなく、コードの量が削減されます。特殊なケースのコードは、アルゴリズムの呼び出しごとに1回だけ実行される傾向があるため、特殊なケースを回避するよりもわずかにパフォーマンスが低下する可能性があります。これは測定する価値があるかもしれないものです。

質問にC++のタグが付けられているにもかかわらず、コード例はC++ではありません。結果として、それは慣用的なC++ではありません。 lower_bound()またはupper_bound()のようなものを実装するためのC++の一般的なアプローチは、適切なイテレータを使用することです。これらのアルゴリズムは、適切な位置にイテレータを生成するだけなので、適切な要素がない場合、「文句を言う」ことはありません。つまり、std::lower_bound()の開始のイテレータと、 std::upper_bound()

0
Dietmar Kühl