基数ソートと計数ソート
問題. 自然数列の整列
要素数 以下の 個の自然数列 が与えられる.これらを辞書式順に昇順に整列せよ.ただし, であるとは, が の先頭部分に一致するか,または, を となる最小の添字としたときに となることである.
制約:
例.
昇順に整列すると となる.
解法. 基数ソート (radix sort)
基数ソートでは部分的に計数ソート(counting sort)を用いるために,初めに計数ソートの説明を行い,その後で基数ソートの説明を行う.
計数ソート(counting sort)
個の自然数 を計数ソートで昇順にソートする(下の疑似コードに合わすために0-indexにした).ここで, として, と仮定する.要素の値が小さいことに着目して,各要素に対して自身の値より小さい要素数を数えることにする.これは累積和を用いると,計算時間と領域量は互いに で求めることができる.
// 入力 const int n = 8; vector<int> a = {4, 1, 2, 6, 4, 1, 1, 0}; // 自身の値より小さな要素数を数える int k = *max_element(a.begin(), a.end()); vector<int> cnt(k + 1); for (int i = 0; i < n; ++i) ++cnt[a[i]]; for (int i = 1; i < n; ++i) cnt[i] += cnt[i - 1]; // cnt = (1, 4, 5, 5, 7, 7, 8)
次に,各要素について自身がソート後に何番目の位置に移動するかを考えると, の位置は区間 に含まれることが分かる.ただし,基数ソートで使用するために安定性(stability)を満たすように位置決めを行う必要がある.一般的にソートが安定であるとは,等しい要素の順番を入力前と同じに保つ性質のことである.安定性を満たすために,後ろの要素から順番に自身が入る場所を見つける.このとき, 番目に挿入して の値を1つ減らすと安定性を満たすように全ての要素を整列することができる.
以上から,計数ソートは全体で の時間と領域で求めることができる.
vector<int> b(n); // aのソーティング後の要素 for (int i = n - 1; 0 <= i; --i) { b[cnt[a[i]] - 1] = a[i]; --cnt[a[i]]; } // b = (0, 1, 1, 1, 2, 4, 4, 6)
まとめ
要素対の比較を行う比較ソートアルゴリズムは最悪時に 回の比較を行うが,基数ソートも計数ソートも比較ソートアルゴリズムではないので下界を打ち破ることができる.
基数ソートは木の同型性判定で, 時間から 時間にするときに用いられる.
参考文献
- T.コルメン,C.ライザーソン,R.リベスト,C.シュタイン(浅野哲夫・岩野和生・梅尾博司・山下雅史・和田幸一 訳):『アルゴリズムイントロダクション 第3版 総合版:世界標準MIT教科書』.近代工学社,東京,2013.