web-dev-qa-db-ja.com

std :: lower_boundおよびstd :: upper_boundの理論的根拠?

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境界を見つけます。

これは理にかなっていますか?どのように覚えていますか?

55
Don Hatch

範囲[firstlast)に複数の要素があり、その値が検索している値valと等しい場合、範囲[lu)ここで

l = std::lower_bound(first, last, val)
u = std::upper_bound(first, last, val)

[valfirst)の範囲内のlastに等しい要素の範囲です。したがって、luは、等しい範囲の「下限」と「上限」です。半開間隔の観点から考えることに慣れているなら、それは理にかなっています。

(ご了承ください std::equal_rangeは、1回の呼び出しで、ペアの下限と上限の両方を返します。

73
Brian
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


http://en.cppreference.com/w/cpp/algorithm/lower_bound

http://en.cppreference.com/w/cpp/algorithm/upper_bound

28
4pie0

この場合、写真は千の言葉に値すると思います。これらを使用して、次のコレクションで2を検索するとします。矢印は、2つが返すイテレータを示しています。

enter image description here

そのため、コレクションに既にその値を持つオブジェクトが複数ある場合、lower_boundは最初のオブジェクトを参照するイテレーターを提供し、upper_boundは参照するイテレーターを提供します最後のオブジェクトの直後のオブジェクト。

これにより(特に)、返される反復子はhintinsertパラメーターとして使用可能になります。

したがって、これらをヒントとして使用すると、挿入するアイテムは、その値を持つ新しい最初のアイテム(lower_boundを使用した場合)またはその値を持つ最後のアイテム(upper_boundを使用した場合)になります。コレクションに以前にその値のアイテムが含まれていなかった場合は、hintとして使用してコレクション内の正しい位置に挿入できるイテレーターを取得できます。

もちろん、ヒントなしで挿入することもできますが、挿入する新しいアイテムをイテレータが指すアイテムの直前に挿入できる場合、ヒントを使用すると、挿入が一定の複雑さで完了することが保証されます(これら両方の場合)。

9
Jerry Coffin

シーケンスを考える

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はそれらの終わりです。

私は彼らがドキュメントでこのように(または与えられた他の良い答えのいずれか)それについて話すことを望みます。そうすれば神秘的ではなくなります!

4
Don Hatch

両方の関数は、ソートを保持するソートされたシーケンスで挿入ポイントを見つけるという点で非常に似ています。シーケンス内に検索項目に等しい既存の要素がない場合、それらは同じイテレータを返します。

シーケンス内で何かを見つけようとしている場合は、lower_boundを使用します。見つかった場合は、要素を直接指します。

シーケンスに挿入する場合は、upper_boundを使用します-複製の元の順序が保持されます。

1
Mark Ransom

配列またはベクトルの場合:

std :: lower_bound:範囲内の最初の要素を指すイテレータを返します

  • 値以下(配列またはベクトルの降順)
  • 値以上(昇順の配列またはベクトルの場合)

std :: upper_bound:範囲内の最初の要素を指すイテレータを返します

  • 値より小さい(配列またはベクトルの降順)

  • 値より大きい(配列またはベクトルの昇順)

0
Vipul Jain

はい。質問には絶対的な意味があります。誰かがこれらの関数に名前を付けたとき、彼らは繰り返し要素を持つソートされた配列のみを考えていました。一意の要素を持つ配列がある場合、「std :: lower_bound()」は、実際の要素が見つからない限り、「上限」の検索のように機能します。

これがこれらの機能について私が覚えていることです:

  • バイナリ検索を行う場合は、std :: lower_bound()の使用を検討し、マニュアルを読んでください。 std :: binary_search()もそれに基づいています。
  • 一意の値のソートされた配列で値の「場所」を検索する場合は、std :: lower_bound()を検討し、マニュアルを読んでください。
  • ソートされた配列を検索する任意のタスクがある場合は、std :: lower_bound()とstd :: upper_bound()の両方のマニュアルをお読みください。

これらの機能を最後に使用してから1、2か月後にマニュアルを読まないと、ほぼ間違いなくバグにつながります。

0

_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のそのようなマッピングがあるとします。このアプローチにより、マップのサイズが小さくなり、マップが効率的になります。

0
shainu vy

[first, last)valに等しい最初の要素を見つけたい場合はどうするか想像してみてください。最初にvalより厳密に小さい最初の要素から除外し、次にlast [-1]から厳密にvalより厳密に大きい要素を除外します。残りの範囲は[lower_bound, upper_bound]です

0
Xuan Liu