STLはバイナリ検索関数std :: lower_boundおよびstd :: upper_boundを提供しますが、それらの契約が完全に神秘的なように見えるため、それらが何をするのか覚えていないため、私はそれらを使用しない傾向があります。
名前を見るだけで、「lower_bound」は「last lower bound」の略語になると思いますが、
i.eソートされたリストの最後の要素で、指定されたval(もしあれば)<=です。
同様に、「upper_bound」は「最初の上限」の略語かもしれませんが、
i.eソートされたリストの最初の要素で、> =指定されたval(存在する場合)。
しかし、ドキュメンテーションは、彼らはそれとはかなり異なる何かを行うと言っています-私には逆方向とランダムの混合物のように見える何か。ドキュメントを言い換えるには:
-lower_boundは、val以上の最初の要素を見つけます
-upper_boundは、> valである最初の要素を見つけます
したがって、lower_boundは下限をまったく見つけません。最初のpper bound !?また、upper_boundは最初のstrict upper境界を見つけます。
これは理にかなっていますか?どのように覚えていますか?
範囲[first
、last
)に複数の要素があり、その値が検索している値val
と等しい場合、範囲[l
、 u
)ここで
l = std::lower_bound(first, last, val)
u = std::upper_bound(first, last, val)
[val
、first
)の範囲内のlast
に等しい要素の範囲です。したがって、l
とu
は、等しい範囲の「下限」と「上限」です。半開間隔の観点から考えることに慣れているなら、それは理にかなっています。
(ご了承ください std::equal_range
は、1回の呼び出しで、ペアの下限と上限の両方を返します。
std::lower_bound
以上)値以上の範囲[first、last)の最初の要素を指す反復子を返します。
std::upper_bound
値よりもgreaterである範囲[first、last)の最初の要素を指す反復子を返します。
したがって、下限と上限の両方を混合することにより、範囲の始まりと終わりを正確に記述することができます。
これは理にかなっていますか?
はい。
例:
ベクトルを想像する
std::vector<int> data = { 1, 1, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 6 };
auto lower = std::lower_bound(data.begin(), data.end(), 4);
1, 1, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 6
// ^ lower
auto upper = std::upper_bound(data.begin(), data.end(), 4);
1, 1, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 6
// ^ upper
std::copy(lower, upper, std::ostream_iterator<int>(std::cout, " "));
プリント:4 4 4
この場合、写真は千の言葉に値すると思います。これらを使用して、次のコレクションで2
を検索するとします。矢印は、2つが返すイテレータを示しています。
そのため、コレクションに既にその値を持つオブジェクトが複数ある場合、lower_bound
は最初のオブジェクトを参照するイテレーターを提供し、upper_bound
は参照するイテレーターを提供します最後のオブジェクトの直後のオブジェクト。
これにより(特に)、返される反復子はhint
のinsert
パラメーターとして使用可能になります。
したがって、これらをヒントとして使用すると、挿入するアイテムは、その値を持つ新しい最初のアイテム(lower_bound
を使用した場合)またはその値を持つ最後のアイテム(upper_bound
を使用した場合)になります。コレクションに以前にその値のアイテムが含まれていなかった場合は、hint
として使用してコレクション内の正しい位置に挿入できるイテレーターを取得できます。
もちろん、ヒントなしで挿入することもできますが、挿入する新しいアイテムをイテレータが指すアイテムの直前に挿入できる場合、ヒントを使用すると、挿入が一定の複雑さで完了することが保証されます(これら両方の場合)。
シーケンスを考える
1 2 3 4 5 6 6 6 7 8 9
6の下限は最初の6の位置です。
6の上限は7の位置です。
これらの位置は、6値の実行を指定する共通(開始、終了)ペアとして機能します。
例:
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
auto main()
-> int
{
vector<int> v = {1, 2, 3, 4, 5, 6, 6, 6, 7, 8, 9};
auto const pos1 = lower_bound( v.begin(), v.end(), 6 );
auto const pos2 = upper_bound( v.begin(), v.end(), 6 );
for( auto it = pos1; it != pos2; ++it )
{
cout << *it;
}
cout << endl;
}
ブライアンの答えを受け入れましたが、わかりやすくするための別の有用な考え方に気付いたので、参照用に追加します。
返されるイテレータは、要素* iterではなく、単にbeforeその要素、つまりbetweenその要素とリスト内の前の要素がある場合はそれを指すと考えてください。そのように考えると、2つの関数のコントラクトは対称になります。lower_boundは<valから> = valへの遷移の位置を見つけ、upper_boundは<= valから> valへの遷移の位置を見つけます。別の言い方をすれば、lower_boundはvalと等しい項目の範囲の始まり(つまりstd :: equal_rangeが返す範囲)であり、upper_boundはそれらの終わりです。
私は彼らがドキュメントでこのように(または与えられた他の良い答えのいずれか)それについて話すことを望みます。そうすれば神秘的ではなくなります!
両方の関数は、ソートを保持するソートされたシーケンスで挿入ポイントを見つけるという点で非常に似ています。シーケンス内に検索項目に等しい既存の要素がない場合、それらは同じイテレータを返します。
シーケンス内で何かを見つけようとしている場合は、lower_bound
を使用します。見つかった場合は、要素を直接指します。
シーケンスに挿入する場合は、upper_bound
を使用します-複製の元の順序が保持されます。
配列またはベクトルの場合:
std :: lower_bound:範囲内の最初の要素を指すイテレータを返します
std :: upper_bound:範囲内の最初の要素を指すイテレータを返します
値より小さい(配列またはベクトルの降順)
値より大きい(配列またはベクトルの昇順)
はい。質問には絶対的な意味があります。誰かがこれらの関数に名前を付けたとき、彼らは繰り返し要素を持つソートされた配列のみを考えていました。一意の要素を持つ配列がある場合、「std :: lower_bound()」は、実際の要素が見つからない限り、「上限」の検索のように機能します。
これがこれらの機能について私が覚えていることです:
これらの機能を最後に使用してから1、2か月後にマニュアルを読まないと、ほぼ間違いなくバグにつながります。
_std::lower_bound
_および_std::upper_bound
_ isについては、すでに適切な回答があります。
あなたの質問「それらを覚える方法」に答えたいですか?
コンテナのSTL begin()
およびend()
メソッドとの類似性を引き出すと、理解しやすく、覚えやすいです。 begin()
はコンテナに開始イテレータを返しますが、end()
はコンテナのすぐ外側にあるイテレータを返します。
これで、ソートされたコンテナと指定された値で、_lower_bound
_と_upper_bound
_は、その値を反復処理しやすい反復子の範囲を返します(開始と終了のように)
実用例::
ソート済みリストでの上記の使用方法とは別に、特定の値の範囲にアクセスするには、upper_boundのより良いアプリケーションの1つは、マップ内で多対1の関係を持つデータにアクセスすることです。
たとえば、1-> a、2-> a、3-> a、4-> b、5-> c、6-> c、7-> c、8-> c、9-の関係を考えます。 > c、10-> c
上記の10個のマッピングは、次のようにマップに保存できます。
_numeric_limits<T>::lowest() : UND
1 : a
4 : b
5 : c
11 : UND
_
式_(--map.upper_bound(val))->second
_によって値にアクセスできます。
Tの値が最低から0の範囲の場合、式はUND
を返します。 Tの値が1〜3の場合、「a」などが返されます。
ここで、1つの値への100のデータマッピングと100のそのようなマッピングがあるとします。このアプローチにより、マップのサイズが小さくなり、マップが効率的になります。
[first, last)
のval
に等しい最初の要素を見つけたい場合はどうするか想像してみてください。最初にval
より厳密に小さい最初の要素から除外し、次にlast [-1]から厳密にval
より厳密に大きい要素を除外します。残りの範囲は[lower_bound, upper_bound]
です