SHOEISHA iD

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

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

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

特集記事

「入れ替えを行わないソート」のおはなし

ダウンロード index_sort.zip (34.5 KB)

順位表を使ったソートの実装

 今回、ソート対象となる要素列内の各要素の入れ替えを行いません。そのかわり「順位表」を作ります。

 例えば要素列double data[4] = { 3.3, 1.1, 2.2, 4.4 }があったとき、同じ要素数のint列 int index[4]を用意して、index[i]が「data[N]をソートしたとき、i番目に位置する要素の添え字(index)」となるようにindex[4]のナカミを埋めよう、と。

list-04
double data[4] = { 3.3, 1.1, 2.2, 4.4 };
int   index[4] = { 1, 2, 0, 3 }; // これを作ることができれば
// 昇順に出力される
for ( int i = 0; i < 4; ++i ) {
  cout << data[index[i]] << endl;
}

 順位表はstd::sortに与える関数オブジェクトをひとひねりすることで簡単に作れます。

 まずint列 int index[N]を用意し、そのナカミを0, 1, 2 ... N-1で埋めます。しかるのちindex[N}をソートするのですが、このときstpd::sortの第3引数には順位表作成用の関数オブジェクトを与えます。関数オブジェクトに渡される2つの値a, b(ソート対象がint列なので型はどちらもint)に対しdata[a] < data[b]のときtrueを返せば、a, bはdata[N]の各要素の位置として扱われますよね。

 index[N = { 0, 1, 2 ... N-1 }をdata[N]の各要素の大小関係に基づいてソートするので、ソート完了後のindex[N]はdata[N]をソートしたときの順位表がindex[N]にできあがります。

list-05
#include <iostream>
#include <iterator>  // begin, end
#include <numeric>   // iota
#include <algorithm> // sort
using namespace std;

int main() {
  const int N = 4;
  double data[N] = { 3.3, 1.1, 2.2, 4.4 };
  int index[N];
  iota(begin(index), end(index), 0); // index を 0, 1, 2 <... N-1 で埋める
  sort(begin(index), end(index),
       [&](int a, int b) { return data[a] < data[b];});

  // 昇順に出力されることを確認
  for ( int i = 0; i < 4; ++i ) {
    cout << index[i] << " : "
         << data[index[i]] << endl;
  }
}

 ソート中に行われる要素の比較が少しばかりややこしくなったことでソートにかかる処理時間が増えているに違いありません、実測してみましょう。通常のソートと順位表方式とで処理時間を比較します。

list-06
#include <iterator>  // begin, end
#include <iostream>  // cout, endl
#include <iomanip>   // setw
#include <ctime>     // clock
#include <numeric>   // iota
#include <algorithm> // sort, is_sorted, generate_n
#include <random>    // mt19937,distribution
#include <chrono>    // chrono
using namespace std;

template<typename Function>
float measure_time(Function f) {
  auto start = chrono::high_resolution_clock::now();
  f();
  auto stop = chrono::high_resolution_clock::now();
  return chrono::duration_cast<chrono::microseconds>(stop - start).count() / 1000.0f;
}

int main() {
  const size_t N = 1000000;
  using type = double;

  // sortの対象となるdouble列を用意する
  vector<type> source(N);
  mt19937 gen(clock());
  uniform_real_distribution<double> dist;
  generate_n(source.begin(), N, [&]() { return dist(gen); });

  std::vector<type> data;

  {
  cout << "normal :\n";
  data.assign(begin(source), end(source));
  float elapsed = measure_time([&]() { std::sort(begin(data), end(data)); });
  cout << (is_sorted(begin(data), end(data)) ? " ok" : " ng") << " : " 
       << setw(10) << elapsed << "[ms]\n";
  }

  {
  cout << "index :\n";
  data.assign(begin(source), end(source));

  vector<int> index(N);
  iota(begin(index), end(index), 0);
  float elapsed = measure_time([&]() {
                   std::sort(begin(index), end(index),
                             [&](int a, int b) { return data[a] < data[b];});
                  });

  vector<type> sorted(data.size());
  transform(begin(index), end(index), begin(sorted), [&](auto n) { return data[n]; });
  cout << (is_sorted(begin(sorted), end(sorted) ) ? " ok" : " ng") << " : " 
       << setw(10) << elapsed << "[ms]\n";
  }
}
fig-01
fig-01

 結果はご覧のとおり、要素数百万で約3倍の処理時間になってます。要素の比較がindexを介した間接参照となるんで仕方ないかな。

次のページ
要素の交換回数による影響

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

特集記事連載記事一覧

もっと読む

この記事の著者

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

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

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

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

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/9543 2016/08/03 14:00

イベント

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

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

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

メールバックナンバー