Menu
Coddy logo textTech

Counting Sort

最終更新

計数ソートは、既知で限られた範囲の整数に対する非比較ソートです。各値が何回出現するかを数え、その計数を使って各値を直接ソート済みの位置に書き込みます。比較は不要です。上の再生を押すと、値が集計され、そして順番に配置される様子を見られます。

計数ソートは O(n + k) の時間で動作します。ここで k は入力値の範囲です。kn よりそれほど大きくないとき、非常に高速で O(n log n) の比較ソートを上回ることがありますが、値の範囲が巨大な場合は O(k) の計数配列によって非実用的になります。

時間計算量と空間計算量

ケース計算量備考
時間O(n + k)n 個の要素、k = 値の範囲
空間O(n + k)計数配列 + 出力配列
安定はい接頭辞和を用いて右から左へ配置する場合
比較?いいえ比較ではなく計数でソートする
最適な用途小さな整数の範囲k が n に近い場合

ステップごとの流れ

ステップ何が起こるか
1計数配列のサイズを決めるために最大値を求める。
2各値が何回出現するかを数える。
3(任意)安定性のために計数を接頭辞和に変換する。
4各値を、出現する回数だけ出力に書き込む。
5出力配列は完全にソートされている。

具体例

[1, 4, 1, 2, 4] をソートする(値は 0 から 4 の範囲なので計数配列は 5 つのスロットを持つ):

パス状態動作
入力を走査count = [0, 2, 1, 0, 2]出現回数を集計: 1 は 2 回、2 は 1 回、4 は 2 回出現する。
接頭辞和count = [0, 2, 3, 3, 5]各スロットは、そのインデックス <= の値がいくつあるかを保持し、最終的な位置を示す。
4 を配置output = [_, _, _, _, 4]右から左へ読む: count[4] = 5 なので 4 はインデックス 4 へ。4 にデクリメントする。
2 を配置output = [_, _, 2, _, 4]count[2] = 3 なので 2 はインデックス 2 へ。2 にデクリメントする。
1 を配置output = [_, 1, 2, _, 4]count[1] = 2 なので 1 はインデックス 1 へ。1 にデクリメントする。
4 を配置output = [_, 1, 2, 4, 4]count[4] = 4 なので、この 4 はインデックス 3 へ。3 にデクリメントする。
1 を配置output = [1, 1, 2, 4, 4]count[1] = 1 なので、この 1 はインデックス 0 へ。配列はソートされた。

計数ソートを使うべきとき

使うべきとき避けるべきとき
整数(または整数に写像できるキー)を、小さく既知の範囲でソートするとき。値の範囲 k が要素数 n よりはるかに大きいとき。
線形の O(n + k) 時間が必要で、追加の配列を許容できるとき。メモリが逼迫しているとき - 計数配列は n によらず O(k) を要する。
サブルーチンとして安定ソートが必要なとき(例: 基数ソートの内部)。キーが浮動小数点数・文字列・整数へ写像できない任意のオブジェクトのとき。
最大値が有界で、事前に安価に計算できるとき。範囲が未知または非有界で、計数配列のサイズを決められないとき。

Counting Sortのコード

Python, JavaScript, Java, C++, Cによるクリーンで実行可能なCounting Sortの実装です。言語を選んでコードをコピーするか、Coddyプレイグラウンドに読み込んだ状態で開けます。

PythonでのCounting Sortのコード

Python
1def counting_sort(a):2    # Works for non-negative integers with a small max value3    counts = [0] * (max(a) + 1)4    for value in a:5        counts[value] += 16    # Prefix sums turn counts into final positions7    for i in range(1, len(counts)):8        counts[i] += counts[i - 1]9    out = [0] * len(a)10    for value in reversed(a):  # reversed keeps equal values stable11        counts[value] -= 112        out[counts[value]] = value13    return out14
15
16nums = [4, 2, 2, 8, 3, 3, 1]17print("Before:", nums)18print("After: ", counting_sort(nums))
このコードをPythonプレイグラウンドで実行

計数ソートのよくある質問

計数ソートの時間計算量は?
計数ソートは O(n + k) です。ここで n は要素数、k は取りうる値の範囲です。k = O(n) のとき、これは線形時間になります。追加で O(n + k) の空間を使用します。
計数ソートは安定ですか?
安定にできます。安定版は計数の接頭辞和を作り、要素を右から左へ配置するため、等しいキーの相対順序が保たれます。ここで示した単純な「値で書き直す」版は正しいソートを生成しますが、主に単純な整数向けに使われます。
計数ソートはいつ使うべきですか?
整数(または整数に写像できるキー)を、小さく既知の範囲でソートするときに使います。値の範囲 k が要素数よりはるかに大きい場合、計数配列はメモリを無駄にするため、比較ソートの方が適しています。
計数ソートと基数ソート(radix sort)の違いは?
計数ソートは 1 回のパスで値全体を対象にソートし、値の範囲と同じ大きさの計数配列を必要とします。基数ソートは桁ごとにソートし、通常は各桁に対して安定な計数ソートを呼び出すため、1 パスあたりの範囲を小さく保てます(例: 10 進数の桁なら 10)。基数ソートは、単一の計数ソートでは非実用的になる大きな値の範囲を扱えます。
なぜ計数ソートは常にクイックソート(quicksort)より速いわけではないのですか?
計数ソートは O(n + k) なので、値の範囲 kn と同程度のときにのみ有利です。k が巨大な場合 - 例えば 0 から 1,000,000,000 の範囲の 100 個の値をソートする場合 - O(k) の計数配列が支配的になりメモリを無駄にしますが、クイックソートのような O(n log n) の比較ソートは高速で空間効率も保たれます。
計数ソートは負の数を扱えますか?
はい、小さなオフセットで扱えます。計数配列を値で直接インデックスする代わりに value - min でインデックスし、最小値がインデックス 0 に写像されるようにします。計数配列のサイズは max - min + 1 になります。このオフセットを忘れるのはよくあるバグで、負の入力でクラッシュします。
Coddy programming languages illustration

Coddy でアルゴリズムをマスターしよう

始める