記事 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
」
二分探索の下限と上限は、順序を壊さずに値を挿入できる最低位置と最高位置です。 (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_bound
とupper_bound
は同じイテレータを返し、それ以外の場合はlower_bound
は検索範囲の最初の要素を参照するイテレータを返しますこれは検索値に相当しますが、upper_bound
は、最後の要素のすぐ後ろにある要素(ある場合)を参照する反復子を返します。
配列が常に
false … true …
その場合、index 0
でtrueを見つけない限り、見つける前のインデックスは常にfalseになります。上記の私のコメントで述べたように、もう1つの境界ケースは、true
が見つからない場合です。次に、最も高い偽が配列の最後の部分になります。
2つのアルゴリズムは、コードスニペットから実際に明らかなように、true
またはfalse
の値がない場合に発生する状況の条件が明らかに異なります。値はtrue
であり、この位置から1を引いてfalse
を生成する最も高い値を見つけます。そのようなオブジェクトがないため、誤った結果が生成されます。アルゴリズムは、適切な要素を特定することを処理する異なる要素を直接対象とするだけなので、特別なケースを持つのではなく、特別なケースを処理する必要がなく、コードの量が削減されます。特殊なケースのコードは、アルゴリズムの呼び出しごとに1回だけ実行される傾向があるため、特殊なケースを回避するよりもわずかにパフォーマンスが低下する可能性があります。これは測定する価値があるかもしれないものです。
質問にC++のタグが付けられているにもかかわらず、コード例はC++ではありません。結果として、それは慣用的なC++ではありません。 lower_bound()
またはupper_bound()
のようなものを実装するためのC++の一般的なアプローチは、適切なイテレータを使用することです。これらのアルゴリズムは、適切な位置にイテレータを生成するだけなので、適切な要素がない場合、「文句を言う」ことはありません。つまり、std::lower_bound()
の開始のイテレータと、 std::upper_bound()
。