1. 序章

このチュートリアルでは、 Radix Sort について学習し、そのパフォーマンスを分析して、その実装を確認します。

ここでは基数ソートを使用して整数をソートすることに焦点を当てていますが、これは数値だけに限定されません。 文字列、などの他のタイプのソートにも使用できます。

簡単にするために、数値が基数(基数)10で表される10進法に焦点を当てます。

2. アルゴリズムの概要

基数ソートは、桁の位置に基づいて数値をソートするソートアルゴリズムです。 基本的には、数値の桁の桁の値を使用します。 マージソート、挿入ソート、バブルソートなど、他のほとんどのソートアルゴリズムとは異なり、数値を比較しません。

基数ソートは、安定ソートアルゴリズムをサブルーチンとして使用して数字をソートします。 ここでは、基数を使用してすべての位置の数字を並べ替えるサブルーチンとして、並べ替えのカウントのバリエーションを使用しました。 Counting sort は安定したソートアルゴリズムであり、実際にはうまく機能します。

基数ソートは、最下位桁(LSD)から最上位桁(MSD)に数字を並べ替えることによって機能します。 MSDからの数字を処理するために基数ソートを実装することもできます。

3. 簡単な例

例を使ってどのように機能するかを見てみましょう。 次の配列を考えてみましょう。

3.1. 反復1

LSDからの数字を処理し、MSDに向かって移動することにより、この配列を並べ替えます。

それでは、1桁の数字から始めましょう。

最初の反復後、配列は次のようになります。

番号は1桁の数字に従ってソートされていることに注意してください。

3.2. 反復2

10の位の数字に移りましょう:

これで、配列は次のようになります。

数字の7は、10の位に数字がないため、配列の最初の位置を占めていることがわかります。 これは、10の位に0があると考えることもできます。

3.3. 反復3

百の位の数字に移りましょう:

この反復の後、配列は次のようになります。

そして、アルゴリズムはここで停止し、すべての要素がソートされます。

4. 実装

次に、実装を見てみましょう。

void sort(int[] numbers) {
    int maximumNumber = findMaximumNumberIn(numbers);
    int numberOfDigits = calculateNumberOfDigitsIn(maximumNumber);
    int placeValue = 1;
    while (numberOfDigits-- > 0) {
        applyCountingSortOn(numbers, placeValue);
        placeValue *= 10;
    }
}

このアルゴリズムは、配列内の最大数を見つけて、その長さを計算することで機能します。 この手順は、すべての場所の値に対してサブルーチンを確実に実行するのに役立ちます。

たとえば、配列 [7、37、68、123、134、221、387、468、769] では、最大数は769で、長さは3です。

したがって、サブルーチンを繰り返して、すべての位置の数字に3回適用します。

void applyCountingSortOn(int[] numbers, int placeValue) {

    int range = 10 // decimal system, numbers from 0-9

    // ...

    // calculate the frequency of digits
    for (int i = 0; i < length; i++) {
        int digit = (numbers[i] / placeValue) % range;
        frequency[digit]++;
    }

    for (int i = 1; i < range; i++) {
        frequency[i] += frequency[i - 1];
    }

    for (int i = length - 1; i >= 0; i--) {
        int digit = (numbers[i] / placeValue) % range;
        sortedValues[frequency[digit] - 1] = numbers[i];
        frequency[digit]--;
    }

    System.arraycopy(result, 0, numbers, 0, length); 

}

サブルーチンでは、基数(range)を使用して、各桁の出現回数をカウントし、その頻度をインクリメントしました。 したがって、0から9の範囲の各ビンには、桁の頻度に基づいた値があります。 次に、周波数を使用して、配列内の各要素を配置します。 これは、配列の並べ替えに必要なスペースを最小限に抑えるのにも役立ちます。

それでは、メソッドをテストしてみましょう。

@Test
public void givenUnsortedArray_whenRadixSort_thenArraySorted() {
    int[] numbers = {387, 468, 134, 123, 68, 221, 769, 37, 7};
    RadixSort.sort(numbers);
    int[] numbersSorted = {7, 37, 68, 123, 134, 221, 387, 468, 769};
    assertArrayEquals(numbersSorted, numbers); 
}

5. 基数ソートとカウントソート

サブルーチンでは、 Frequency 配列の長さは10(0-9)です。 カウントソートの場合、rangeは使用しません。 frequency 配列の長さは、配列内の最大数+1になります。 したがって、それらをビンに分割しませんが、基数ソートはビンを使用してソートします。

配列の長さが配列の最大値よりもそれほど小さくない場合、ソートのカウントは非常に効率的ですが、基数ソートでは配列の値を大きくすることができます。

6. 複雑

基数ソートのパフォーマンスは、数字をソートするために選択された安定したソートアルゴリズムに依存します。

ここでは、基数ソートを使用して、ベースbn数値の配列をソートしました。 この場合、ベースは10です。 カウントソートd回を適用しました。ここで、dは桁数を表します。 したがって、基数ソートの時間計算量は O(d *(n + b))になります。

ここでは、Counting Sortのバリエーションをサブルーチンとして使用しているため、スペースの複雑さは O(n + b)です。

7. 結論

この記事では、基数ソートアルゴリズムについて説明し、その実装方法を説明しました。

いつものように、コードの実装はGithub利用できます。