SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

「ソートも、サーチも、あるんだよ」
~標準C++ライブラリにみるアルゴリズムの面白さ

<algorithm>のソートたち


ソート列に対する二分検索

 ところで僕らはなぜソートするのでしょうか。大きな理由の一つは「ヒトが順序正しく並んだ列を求めるから」、もう一つはより速く検索するためです。N個の要素がデタラメに並んでいるとき、特定の要素がその中に存在するか否かを判定するのに要する時間は(結局アタマからケツまで順に調べるしか判定するすべがないのだから)要素数Nに比例します。時間計算量Ο(N)ですね。一方、N個の要素がソートされていれば二分検索ができるのでΟ(logN)になります。Ο(N)に比べれば劇的なスピードアップが見込まれます。

 <algorithm> が提供する(ソートされた列に対する)二分検索アルゴリズムは binary_search, lower_bound, upper_bound, equal_range の4つ。順に見ていきましょう。

binary_search

 ソートされた要素列内に指定した要素が存在すればtrue,さもなくばfalseを返します。

array<int,10> a = { 0, 1, 2, 2, 5, 7, 7, 8, 8, 9 };

// 5, 3 は配列内にあるか?
bool found;
found = binary_search(a.begin(), a.end(), 5);
cout << "5: " << (found ? "found." : "not found.") << endl;
found = binary_search(a.begin(), a.end(), 3);
cout << "3: " << (found ? "found." : "not found.") << endl;

 要素の有無を調べるだけなら binary_search() で十分ですが、binary_search() は見つかった位置を教えてはくれません。それが知りたいなら lower_bound, upper_bound, equal_range を使うことになります。

lower_bound, upper_bound, equal_range

 ソートされた列に新たな要素を挿入する際の挿入位置を返してくれます。

 例えば列: { 0, 1, 2, 2, 5, 7, 7, 8, 8, 9 } に 2 を挿入するとき、

{ 0, 1, 2, 2, 5, 7, 7, 8, 8, 9 }

 下線を引いた要素の直前に挿入可能ですね。lower_bound() は列の先頭に最も近い挿入位置、upper_bound() は先頭から最も遠い挿入位置を返します。equal_range() は lower_bound() と upper_bound() の結果の pair、すなわち指定した要素に等しい要素列の範囲を返してくれます。

 同様に 6 を挿入するなら:

{ 0, 1, 2, 2, 5, 7, 7, 8, 8, 9 }

 lower_bound(), upper_bound() 共に下線要素の位置を返します。

template<typename Iterator>
void print(Iterator first, Iterator last, Iterator lower, Iterator upper) {
  for ( Iterator iter = first; iter != last; ++iter ) {
    if ( iter == lower && iter == upper ) { cout << "[]"; }
    else if ( iter == lower ) { cout << " ["; }
    else if ( iter == upper ) { cout << "] "; }
    else cout << "  ";
    cout << *iter;
  }
  cout << endl;
}
...
array<int,10> a = { 0, 1, 2, 2, 5, 7, 7, 8, 8, 9 };
array<int,10>::iterator lower, upper;

// 配列内での 2 の位置
lower = lower_bound(a.begin(), a.end(), 2);
upper = upper_bound(a.begin(), a.end(), 2);
cout << "2: ";
print(a.begin(), a.end(), lower, upper);

// 配列内での 6 の位置
lower = lower_bound(a.begin(), a.end(), 6);
upper = upper_bound(a.begin(), a.end(), 6);
cout << "6: ";
print(a.begin(), a.end(), lower, upper);

// 配列内での 2 の位置
auto range = equal_range(a.begin(), a.end(), 2);
lower = range.first;
upper = range.second;
cout << "2: ";
print(a.begin(), a.end(), lower, upper);

// 配列内での 6 の位置
range = equal_range(a.begin(), a.end(), 6);
lower = range.first;
upper = range.second;
cout << "6: ";
print(a.begin(), a.end(), lower, upper);

次のページ
ソート列を対象とする集合演算

この記事は参考になりましたか?

特集記事連載記事一覧

もっと読む

この記事の著者

επιστημη(エピステーメー)

C++に首まで浸かったプログラマ。Microsoft MVP, Visual C++ (2004.01~2018.06) "だった"りわんくま同盟でたまにセッションスピーカやったり中国茶淹れてにわか茶...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/6020 2011/07/28 08:09

おすすめ

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー