C言語で基数ソートを実装する方法と詳細解説

基数ソートは、特定の種類のデータセットに対して非常に効率的なソートアルゴリズムです。このアルゴリズムは、整数のソートに特に適しており、安定ソートであるため、同じ値の要素の順序を保持します。この記事では、C言語を使用して基数ソートを実装する方法をステップバイステップで解説し、その仕組みと応用例を詳しく紹介します。

目次

基数ソートの概要

基数ソートは、整数を桁ごとにソートすることで全体を整列するソートアルゴリズムです。このアルゴリズムは、リストの各要素を同じ桁数に揃えた後、最下位桁から最上位桁に向かって順にソートを行います。これにより、各桁ごとに安定ソートを繰り返し、最終的に全体がソートされます。基数ソートは比較ベースのソートアルゴリズムとは異なり、計算量が一定の範囲内であるため、大規模なデータセットに対しても効率的に動作します。

必要な前提知識

基数ソートを理解し、C言語で実装するためには、以下の前提知識が必要です。

C言語の基礎知識

変数、データ型、ループ構造(for、while)、条件分岐(if、switch)、関数の定義と呼び出しなど、C言語の基本的な構文を理解していることが重要です。

配列の操作

配列の宣言、初期化、アクセス方法、二次元配列の使用など、配列に関する操作方法を理解していることが求められます。

安定ソートの理解

基数ソートは安定ソートであるため、バブルソートや挿入ソートなどの安定ソートの動作原理を理解していると、基数ソートの動作理解が容易になります。

ビット演算の基礎

基数ソートでは、整数の桁を扱うためにビット操作が頻繁に使われます。ビットシフト演算やビットマスクの基本的な使い方を知っていることが有用です。

これらの知識を前提に、基数ソートのアルゴリズムとそのC言語での実装に取り組むことができます。

基数ソートのアルゴリズム

基数ソートのアルゴリズムは、数値を桁ごとにソートすることに基づいており、最も下位の桁から順に上位の桁へとソートを繰り返します。以下に基数ソートの具体的な手順を説明します。

ステップ1: 最大桁数の決定

ソート対象の配列の中で最大の数値を見つけ、その数値の桁数を確認します。これにより、ソートする桁数が決まります。

ステップ2: 各桁ごとのソート

最下位桁から始めて、各桁ごとに安定ソートを実行します。安定ソートにはバケットソートやカウントソートを使用するのが一般的です。

ステップ2.1: 桁の抽出

各数値から現在の桁の値を抽出します。例えば、10の位を抽出するには、その数値を10で割った余りを計算します。

ステップ2.2: バケットに振り分け

抽出した桁の値に基づいて、数値を対応するバケットに振り分けます。0から9までの10個のバケットを使用します。

ステップ2.3: バケットの統合

全てのバケットを順番に取り出し、元の配列に再配置します。この操作により、現在の桁についてソートが完了します。

ステップ3: 次の桁に進む

全ての桁についてソートが完了するまで、桁を上げてステップ2を繰り返します。最上位桁までソートが完了した時点で、全体がソートされます。

このプロセスにより、基数ソートは配列を効率的にソートします。次に、C言語での実装手順について詳しく見ていきます。

C言語での実装手順

ここでは、C言語を使用して基数ソートを実装するための具体的なステップを説明します。以下の手順に従って、基数ソートを実装してみましょう。

ステップ1: ヘッダーファイルのインクルード

標準ライブラリをインクルードします。ここでは、入力と出力を行うためにstdio.hを使用します。

#include <stdio.h>

ステップ2: 最大桁数を取得する関数の作成

配列の中で最大の数値を見つけ、その桁数を計算する関数を作成します。

int getMax(int arr[], int n) {
    int max = arr[0];
    for (int i = 1; i < n; i++)
        if (arr[i] > max)
            max = arr[i];
    return max;
}

ステップ3: 基数ソートの補助関数(カウントソート)の作成

基数ソートで使用するカウントソートの関数を作成します。この関数は、指定された桁に基づいて配列をソートします。

void countSort(int arr[], int n, int exp) {
    int output[n]; // 出力配列
    int count[10] = {0}; // カウント配列

    // 指定された桁の出現回数を数える
    for (int i = 0; i < n; i++)
        count[(arr[i] / exp) % 10]++;

    // カウント配列を変更して実際の位置を示す
    for (int i = 1; i < 10; i++)
        count[i] += count[i - 1];

    // 出力配列を構築
    for (int i = n - 1; i >= 0; i--) {
        output[count[(arr[i] / exp) % 10] - 1] = arr[i];
        count[(arr[i] / exp) % 10]--;
    }

    // 出力配列を元の配列にコピー
    for (int i = 0; i < n; i++)
        arr[i] = output[i];
}

ステップ4: 基数ソートのメイン関数の作成

基数ソートのメイン関数を作成し、カウントソートを桁ごとに呼び出します。

void radixSort(int arr[], int n) {
    int m = getMax(arr, n); // 最大の数を取得

    // 各桁についてカウントソートを行う
    for (int exp = 1; m / exp > 0; exp *= 10)
        countSort(arr, n, exp);
}

ステップ5: メイン関数の作成

ソートする配列を定義し、基数ソート関数を呼び出します。

int main() {
    int arr[] = {170, 45, 75, 90, 802, 24, 2, 66};
    int n = sizeof(arr) / sizeof(arr[0]);
    radixSort(arr, n);
    printf("Sorted array: ");
    for (int i = 0; i < n; i++)
        printf("%d ", arr[i]);
    return 0;
}

この手順を実行することで、C言語で基数ソートを実装することができます。次に、各部分のコードの詳細説明を行います。

コードの詳細説明

基数ソートのC言語実装について、各部分のコードを詳細に説明します。

ヘッダーファイルのインクルード

#include <stdio.h>

標準入力および出力関数を使用するために、stdio.hをインクルードします。

最大値を取得する関数

int getMax(int arr[], int n) {
    int max = arr[0];
    for (int i = 1; i < n; i++)
        if (arr[i] > max)
            max = arr[i];
    return max;
}

この関数は、配列arrの中で最大の値を見つけ、それを返します。これにより、ソートする際に必要な桁数を知ることができます。

カウントソートの関数

void countSort(int arr[], int n, int exp) {
    int output[n]; // 出力配列
    int count[10] = {0}; // カウント配列

    // 指定された桁の出現回数を数える
    for (int i = 0; i < n; i++)
        count[(arr[i] / exp) % 10]++;

    // カウント配列を変更して実際の位置を示す
    for (int i = 1; i < 10; i++)
        count[i] += count[i - 1];

    // 出力配列を構築
    for (int i = n - 1; i >= 0; i--) {
        output[count[(arr[i] / exp) % 10] - 1] = arr[i];
        count[(arr[i] / exp) % 10]--;
    }

    // 出力配列を元の配列にコピー
    for (int i = 0; i < n; i++)
        arr[i] = output[i];
}

この関数は、与えられた桁(exp)に基づいて配列をソートします。まず、指定された桁の値の出現回数を数え、それに基づいて各要素の位置を決定し、最終的に出力配列にソートされた要素を配置します。

基数ソートのメイン関数

void radixSort(int arr[], int n) {
    int m = getMax(arr, n); // 最大の数を取得

    // 各桁についてカウントソートを行う
    for (int exp = 1; m / exp > 0; exp *= 10)
        countSort(arr, n, exp);
}

この関数は、配列の最大値を取得し、最下位桁から始めて各桁ごとにカウントソートを実行します。expは現在の桁の重みを示し、10の累乗で増加します。

メイン関数

int main() {
    int arr[] = {170, 45, 75, 90, 802, 24, 2, 66};
    int n = sizeof(arr) / sizeof(arr[0]);
    radixSort(arr, n);
    printf("Sorted array: ");
    for (int i = 0; i < n; i++)
        printf("%d ", arr[i]);
    return 0;
}

この関数は、ソート対象の配列を定義し、基数ソートを実行します。ソートが完了した後、結果を出力します。

このコードの詳細説明を理解することで、基数ソートの動作原理とC言語での実装方法をより深く理解することができます。

実行例と結果

ここでは、前述の基数ソートの実装を実行し、その結果を確認します。基数ソートは桁ごとにソートを行うため、各ステップでの配列の状態を観察することでアルゴリズムの動作を理解しやすくなります。

実行例

次のコードを実行してみましょう:

#include <stdio.h>

int getMax(int arr[], int n) {
    int max = arr[0];
    for (int i = 1; i < n; i++)
        if (arr[i] > max)
            max = arr[i];
    return max;
}

void countSort(int arr[], int n, int exp) {
    int output[n];
    int count[10] = {0};

    for (int i = 0; i < n; i++)
        count[(arr[i] / exp) % 10]++;

    for (int i = 1; i < 10; i++)
        count[i] += count[i - 1];

    for (int i = n - 1; i >= 0; i--) {
        output[count[(arr[i] / exp) % 10] - 1] = arr[i];
        count[(arr[i] / exp) % 10]--;
    }

    for (int i = 0; i < n; i++)
        arr[i] = output[i];
}

void radixSort(int arr[], int n) {
    int m = getMax(arr, n);

    for (int exp = 1; m / exp > 0; exp *= 10)
        countSort(arr, n, exp);
}

int main() {
    int arr[] = {170, 45, 75, 90, 802, 24, 2, 66};
    int n = sizeof(arr) / sizeof(arr[0]);
    printf("Original array: ");
    for (int i = 0; i < n; i++)
        printf("%d ", arr[i]);
    printf("\n");

    radixSort(arr, n);

    printf("Sorted array: ");
    for (int i = 0; i < n; i++)
        printf("%d ", arr[i]);
    return 0;
}

結果の確認

このコードを実行すると、次のような出力が得られます:

Original array: 170 45 75 90 802 24 2 66
Sorted array: 2 24 45 66 75 90 170 802

結果の詳細

  • Original array: ソート前の配列です。配列の要素は、ランダムな順序で並んでいます。
  • Sorted array: 基数ソートを適用した後の配列です。全ての要素が昇順に並べ替えられています。

この実行例からわかるように、基数ソートは配列内の整数を効率的にソートします。特に、桁ごとにソートを行うことで、安定した結果が得られることが確認できます。この結果をもとに、基数ソートの実用性とその効果を理解することができます。

応用例

基数ソートは整数のソートに特化したアルゴリズムですが、その応用範囲は広く、さまざまな場面で利用することができます。ここでは、基数ソートの応用例をいくつか紹介します。

文字列のソート

基数ソートは文字列のソートにも応用できます。文字列の各文字を一桁として扱い、最下位の文字から順にソートすることで、アルファベット順に並べ替えることができます。

#include <stdio.h>
#include <string.h>

void countSortStrings(char arr[][10], int n, int index) {
    char output[n][10];
    int count[256] = {0};

    for (int i = 0; i < n; i++)
        count[(int)arr[i][index]]++;

    for (int i = 1; i < 256; i++)
        count[i] += count[i - 1];

    for (int i = n - 1; i >= 0; i--) {
        strcpy(output[count[(int)arr[i][index]] - 1], arr[i]);
        count[(int)arr[i][index]]--;
    }

    for (int i = 0; i < n; i++)
        strcpy(arr[i], output[i]);
}

void radixSortStrings(char arr[][10], int n, int maxLen) {
    for (int index = maxLen - 1; index >= 0; index--)
        countSortStrings(arr, n, index);
}

int main() {
    char arr[][10] = {"apple", "banana", "grape", "kiwi", "orange", "pear"};
    int n = sizeof(arr) / sizeof(arr[0]);
    int maxLen = 10;

    radixSortStrings(arr, n, maxLen);

    printf("Sorted array: \n");
    for (int i = 0; i < n; i++)
        printf("%s\n", arr[i]);

    return 0;
}

日付のソート

基数ソートを利用して、日付(年、月、日)をソートすることも可能です。年、月、日をそれぞれの桁として扱い、日付をソートします。

#include <stdio.h>

typedef struct {
    int year;
    int month;
    int day;
} Date;

void countSortDates(Date arr[], int n, int exp, int (*getKey)(Date)) {
    Date output[n];
    int count[10] = {0};

    for (int i = 0; i < n; i++)
        count[(getKey(arr[i]) / exp) % 10]++;

    for (int i = 1; i < 10; i++)
        count[i] += count[i - 1];

    for (int i = n - 1; i >= 0; i--) {
        output[count[(getKey(arr[i]) / exp) % 10] - 1] = arr[i];
        count[(getKey(arr[i]) / exp) % 10]--;
    }

    for (int i = 0; i < n; i++)
        arr[i] = output[i];
}

int getYear(Date d) { return d.year; }
int getMonth(Date d) { return d.month; }
int getDay(Date d) { return d.day; }

void radixSortDates(Date arr[], int n) {
    for (int exp = 1; exp <= 10000; exp *= 10) // Assuming years are within 10000
        countSortDates(arr, n, exp, getYear);
    for (int exp = 1; exp <= 12; exp *= 10)
        countSortDates(arr, n, exp, getMonth);
    for (int exp = 1; exp <= 31; exp *= 10)
        countSortDates(arr, n, exp, getDay);
}

int main() {
    Date arr[] = {{2021, 3, 15}, {2019, 7, 22}, {2020, 11, 5}, {2018, 1, 19}};
    int n = sizeof(arr) / sizeof(arr[0]);

    radixSortDates(arr, n);

    printf("Sorted dates: \n");
    for (int i = 0; i < n; i++)
        printf("%d-%02d-%02d\n", arr[i].year, arr[i].month, arr[i].day);

    return 0;
}

大規模データセットのソート

基数ソートは、特に大規模な整数データセットのソートに非常に有効です。例えば、数百万の整数をソートする場合でも、計算量がO(n)であるため、非常に効率的に処理できます。

これらの応用例からわかるように、基数ソートは整数ソートに限らず、文字列や日付など、さまざまなデータのソートに応用できる汎用的なアルゴリズムです。これを活用することで、多様なソートのニーズに対応することができます。

演習問題

基数ソートの理解を深めるために、以下の演習問題を解いてみましょう。これらの問題に取り組むことで、基数ソートの実装スキルをさらに向上させることができます。

問題1: 配列のソート

以下の整数配列を基数ソートを用いてソートしてください。

int arr[] = {329, 457, 657, 839, 436, 720, 355};

期待される結果:

Sorted array: 329, 355, 436, 457, 657, 720, 839

問題2: 負の整数のソート

基数ソートを修正して、負の整数を含む配列をソートできるようにしてください。

int arr[] = {-170, 45, -75, 90, -802, 24, -2, 66};

期待される結果:

Sorted array: -802, -170, -75, -2, 24, 45, 66, 90

問題3: 浮動小数点数のソート

基数ソートを用いて、浮動小数点数の配列をソートしてください。ただし、浮動小数点数を整数として扱うために適切な変換を行う必要があります。

float arr[] = {329.45, 457.12, 657.34, 839.21, 436.67, 720.89, 355.01};

期待される結果:

Sorted array: 329.45, 355.01, 436.67, 457.12, 657.34, 720.89, 839.21

問題4: 文字列のソート

基数ソートを用いて、以下の文字列配列をアルファベット順にソートしてください。

char arr[][10] = {"apple", "banana", "grape", "kiwi", "orange", "pear"};

期待される結果:

Sorted array: apple, banana, grape, kiwi, orange, pear

問題5: 日付のソート

基数ソートを用いて、以下の日付配列を年月日順にソートしてください。

Date arr[] = {{2021, 3, 15}, {2019, 7, 22}, {2020, 11, 5}, {2018, 1, 19}};

期待される結果:

Sorted dates: 2018-01-19, 2019-07-22, 2020-11-05, 2021-03-15

これらの演習問題に取り組むことで、基数ソートの理解を深め、さまざまなデータ型やケースに適用するスキルを習得することができます。解答例やヒントを参考にしながら、実装してみてください。

まとめ

基数ソートは、特定の種類のデータセットに対して非常に効率的なソートアルゴリズムです。整数や文字列、日付などのデータを安定してソートすることができ、大規模なデータセットにも対応可能です。この記事では、C言語を使用した基数ソートの実装方法とその詳細を解説しました。各ステップを理解し、応用例や演習問題に取り組むことで、基数ソートの原理と実装スキルをさらに深めることができます。これからも様々なアルゴリズムを学び、プログラミングのスキルを向上させていきましょう。

コメント

コメントする

目次