概要
ソート対象となるデータの種類を仮定しない場合、 (ほとんどの場面で)最速のソートは「クイックソート」です。 あるいは、「安定」が必要な場合、 「マージソート」が使われます。 これらはいずれも計算量 O(n log n) のソートですが、 対象データの種類を仮定しない物ではこの O(n log n) がオーダーの限界です。
しかしながら、 データの種類を
-
整数(少なくとも、ソート順を決めるキーが整数)
-
整数値の範囲が予め分かっていて、それほど大きくない
という特殊な場合に限定するならば、 計算量 O(n) でソートが可能です。
バケットソート(bucket sort)は、 このような限定的な条件下で O(n) を実現できるソートの1つです。
バケットソートと呼ばれていますが、bucket というのはバケツ(要は、入れ物)のことです。 ソート対象の整数値が分かっているなら、 まず、その値の数だけバケツを用意します。 そして、
-
値 x が来たら、x 番目のバケツに入れる。
-
全ての値を入れ終わったら、バケツに入った値を前から順に繋ぐ。
という操作で、ソートが行えます。
サンプルソース
https://github.com/ufcpp/UfcppSample/blob/master/Chapters/Algorithm/Sort/BucketSort.cs
値を本当に整数に限定するなら、 (同じ値の要素が複数ある場合、それぞれに区別がないので) 「値 x が来たら、x 番目のバケツに入れる」という操作は 「値 x の数をカウントする」という操作に置き換えることができます。 従って、バケットソートのプログラムは非常に簡単になり、 以下のようになります。
/// <summary>
/// [0, max] の範囲の整数をバケットソート。
/// </summary>
/// <param name="a">対象の配列</param>
/// <param name="max">配列 a 中の最大値</param>
public static void BucketSort(int[] a, int max)
{
// バケツを用意
int[] bucket = new int[max + 1];
// バケツに値を入れる
for (int i = 0; i < a.Length; ++i) ++bucket[a[i]];
// バケツ中の値の結合
for (int j = 0, i = 0; j < bucket.Length; ++j)
for (int k = bucket[j]; k != 0; --k, ++i)
a[i] = j;
}
これに対して、例えば、整数をキーとするデータ構造をソートするなら、 (キーが同じ値でも、キー以外のデータが異なる可能性があるので) 「キーの値が x の要素を入れるバケツ」を「連結リスト」などを使って実装する必要があります。
using System.Collections.Generic;
/// <summary>
/// [0, max] の範囲の整数をキーに持つデータ構造をバケットソート。
/// </summary>
/// <typeparam name="T">値の型</typeparam>
/// <param name="a">対象の配列</param>
/// <param name="max">キーの最大値</param>
public static void BucketSort<T>(KeyValuePair<int, T>[] a, int max)
{
// バケツを用意
List<T>[] bucket = new List<T>[max + 1];
// バケツに値を入れる
for (int i = 0; i < a.Length; ++i)
{
if (bucket[a[i].Key] == null) bucket[a[i].Key] = new List<T>();
bucket[a[i].Key].Add(a[i].Value);
}
// バケツ中の値の結合
for (int j = 0, i = 0; j < bucket.Length; ++j)
if(bucket[j] != null)
foreach (T val in bucket[j])
a[i++] = new KeyValuePair<int, T>(j, val);
}
KeyValuePair
や List
は、
.NET Framework 標準ライブラリの物を使用しています。
先ほどの整数限定版と比べると、
オーバーヘッドが掛かってしまいますが、
それでもオーダーが O(n) で、他のソートアルゴリズムと比べてかなり高速です。