目次

キーワード

概要

バケットソート」は、 値の範囲が限られた整数限定で、計算量 O(n) で極めて高速にソートを行えるアルゴリズムでした。 ですが、「値の範囲が限られた」が曲者で、用途が非常に限定されてしまいます。

基数ソート(radix sort)は、 この欠点を少しでもマシにした、 「バケットソート」の改良版ともいえるアルゴリズムです。

基数(radix)というのは、10進数の10、16進数の16というように、 桁上がりの基準になる数のことです。 基数ソートの発想は、要するに、 「桁ごとに「バケットソート」を繰り返す」というものです。 そうすることで、必要となるバケツの数を基数分(10進数で1桁ずつソートするなら10個で OK)に抑えられます。

基数ソートでも、もちろん、 ソートできる値の桁数に制限が生じますが、 コンピュータ上で扱える整数の桁なんてたかが知れている (例えば、32ビット整数で、10進数10桁) ので、 実質上は、整数なら値の範囲を気にせず、計算量 O(n) でソートができます。

サンプルソース

https://github.com/ufcpp/UfcppSample/blob/master/Chapters/Algorithm/Sort/RadixSort.cs

概念説明のために、 まずは基数を10として、3桁までしかソートできない簡易版のソースを示します。

/// <summary>
/// 基数ソート。
/// 概念説明用の簡易版。
/// 10進数で3桁(0~999)までしかソートできない。
/// </summary>
/// <param name="a">対象の配列</param>
/// <param name="max">配列 a 中の最大値</param>
public static void RadixSort10(int[] a)
{
  // バケツを用意
  List<int>[] bucket = new List<int>[10];

  for (int d = 0, r = 1; d < 3; ++d, r *= 10)
  {
    // バケツに値を入れる
    for (int i = 0; i < a.Length; ++i)
    {
      int key = (a[i] / r) % 10; // a[i] の d 桁目だけを取り出す。
      if (bucket[key] == null) bucket[key] = new List<int>();
      bucket[key].Add(a[i]);
    }

    // バケツ中の値の結合
    for (int j = 0, i = 0; j < bucket.Length; ++j)
      if (bucket[j] != null)
        foreach (int val in bucket[j])
          a[i++] = val;

    // バケツを一度空にする
    for (int j = 0; j < bucket.Length; ++j)
      bucket[j] = null;
  }
}

「バケツに値を入れる」とか「バケツ中の値の結合」の部分は、 「バケットソート」と全く同じ物です。 それを、下の桁から順に3回(=3桁)繰り返しています。

実装上、除算/剰余算は低速なので使いたくないので、 基数には256などの2の冪を使い、 除算/剰余算の代わりにシフト/マスク演算を使います。 基数を256(=1バイト)にした場合、 32ビット整数は4桁(=4バイト)なので、4回の反復で OK です。

/// <summary>
/// 基数ソート。
/// </summary>
/// <param name="a">対象の配列</param>
/// <param name="max">配列 a 中の最大値</param>
public static void RadixSort(int[] a)
{
  // バケツを用意
  List<int>[] bucket = new List<int>[256];

  for (int d = 0, logR = 0; d < 4; ++d, logR += 8)
  {
    // バケツに値を入れる
    for (int i = 0; i < a.Length; ++i)
    {
      int key = (a[i] >> logR) & 255; // a[i] を256進 d 桁目だけを取り出す。
      if (bucket[key] == null) bucket[key] = new List<int>();
      bucket[key].Add(a[i]);
    }

    // バケツ中の値の結合
    for (int j = 0, i = 0; j < bucket.Length; ++j)
      if (bucket[j] != null)
        foreach (int val in bucket[j])
          a[i++] = val;

    // バケツを一度空にする
    for (int j = 0; j < bucket.Length; ++j)
      bucket[j] = null;
  }
}

KeyValuePairList は、 .NET Framework 標準ライブラリの物を使用しています。

更新履歴

ブログ