アルゴリズム

[C言語] 分布数え上げソートを実装する方法

分布数え上げソート(カウントソート)は、整数の範囲が限られている場合に効率的なソートアルゴリズムです。

C言語で実装する際の基本的な手順は以下の通りです。

まず、入力配列の最大値と最小値を見つけ、その範囲に基づいてカウント配列を作成します。

次に、入力配列の各要素の出現回数をカウント配列に記録し、累積和を計算します。

最後に、累積和に基づいて入力配列をソートされた出力配列に再配置します。

分布数え上げソートとは

分布数え上げソート(Counting Sort)は、整数の範囲が限られている場合に特に効率的なソートアルゴリズムです。

このアルゴリズムは、入力データの各要素の出現回数をカウントし、その情報を基にソートされた配列を生成します。

分布数え上げソートは、時間計算量が \(O(n + k)\) であり、ここで \(n\) は入力データの数、\(k\) はデータの範囲を示します。

このため、データの範囲が小さい場合に非常に高速に動作します。

特に、整数や特定の範囲に制約のあるデータのソートに適しています。

分布数え上げソートのアルゴリズム

アルゴリズムの基本的な流れ

分布数え上げソートのアルゴリズムは、以下の基本的な流れで実行されます。

  1. 入力データの最大値を見つける。
  2. カウント配列を作成し、各要素の出現回数をカウントする。
  3. カウント配列を基に累積和を計算する。
  4. ソートされた配列を生成するために、元の配列を逆順に走査し、カウント配列を参照して位置を決定する。

この流れにより、効率的にデータをソートすることが可能です。

カウント配列の作成

カウント配列は、入力データの各要素の出現回数を記録するための配列です。

カウント配列のサイズは、入力データの最大値に基づいて決定されます。

具体的には、次の手順で作成します。

  1. 入力データの最大値を求める。
  2. 最大値に基づいてカウント配列を初期化する。

すべての要素を0に設定する。

  1. 入力データを走査し、各要素の出現回数をカウント配列に記録する。

このカウント配列により、各要素の出現頻度を把握することができます。

累積和の計算

累積和の計算は、カウント配列を基にして行います。

累積和を計算することで、各要素がソートされた配列のどの位置に配置されるかを決定します。

具体的な手順は以下の通りです。

  1. カウント配列の最初の要素はそのままにし、2番目の要素から始めて、前の要素と加算していく。
  2. この操作をカウント配列の最後の要素まで繰り返す。

この結果、カウント配列の各要素は、元の配列におけるその要素の最終的な位置を示すことになります。

ソートされた配列の生成

ソートされた配列を生成するためには、以下の手順を実行します。

  1. 元の配列を逆順に走査する。
  2. 各要素に対して、カウント配列を参照し、その位置に要素を配置する。
  3. 配置後、カウント配列の該当要素を1減少させる。

このプロセスにより、元の配列の要素がソートされた順序で新しい配列に配置されます。

分布数え上げソートは、安定なソートアルゴリズムであるため、同じ値の要素の順序が保持されます。

C言語での分布数え上げソートの実装手順

必要な変数の準備

分布数え上げソートを実装するためには、以下の変数を準備します。

変数名説明
inputArray[]ソート対象の入力配列
countArray[]各要素の出現回数を記録する配列
outputArray[]ソートされた結果を格納する配列
maxValue入力配列の最大値
n入力配列の要素数

カウント配列の初期化

カウント配列を初期化するためには、以下の手順を実行します。

  1. 入力配列の最大値を求める。
  2. 最大値に基づいてカウント配列のサイズを決定する。
  3. カウント配列を0で初期化する。

以下は、カウント配列の初期化を行うサンプルコードです。

#include <stdio.h>
void initializeCountArray(int countArray[], int maxValue) {
    for (int i = 0; i <= maxValue; i++) {
        countArray[i] = 0; // カウント配列を0で初期化
    }
}

入力配列の要素をカウントする

入力配列の要素をカウントするためには、次の手順を実行します。

  1. 入力配列を走査し、各要素の出現回数をカウント配列に記録する。

以下は、要素をカウントするサンプルコードです。

void countElements(int inputArray[], int countArray[], int n) {
    for (int i = 0; i < n; i++) {
        countArray[inputArray[i]]++; // 各要素の出現回数をカウント
    }
}

累積和を計算する

累積和を計算するためには、カウント配列を更新します。

以下のサンプルコードを参照してください。

void calculateCumulativeSum(int countArray[], int maxValue) {
    for (int i = 1; i <= maxValue; i++) {
        countArray[i] += countArray[i - 1]; // 累積和を計算
    }
}

ソートされた配列を生成する

ソートされた配列を生成するためには、元の配列を逆順に走査し、カウント配列を参照して要素を配置します。

以下はそのサンプルコードです。

void generateSortedArray(int inputArray[], int countArray[], int outputArray[], int n) {
    for (int i = n - 1; i >= 0; i--) {
        outputArray[countArray[inputArray[i]] - 1] = inputArray[i]; // ソートされた配列を生成
        countArray[inputArray[i]]--; // カウントを減少
    }
}

実装のポイントと注意点

  • 安定性: 分布数え上げソートは安定なソートアルゴリズムです。

同じ値の要素の順序が保持されます。

  • メモリ使用量: カウント配列のサイズは最大値に依存するため、範囲が広いデータには不向きです。
  • データの範囲: 整数の範囲が限られている場合に最も効果的です。

範囲が広い場合は、他のソートアルゴリズムを検討する必要があります。

分布数え上げソートの時間・空間計算量

時間計算量の解析

分布数え上げソートの時間計算量は、主に以下の3つのステップに依存します。

  1. カウント配列の作成: 入力配列の要素を走査してカウントするため、これは \(O(n)\) の時間がかかります。
  2. 累積和の計算: カウント配列を走査して累積和を計算するため、これも \(O(k)\) の時間がかかります。

ここで \(k\) は入力データの最大値です。

  1. ソートされた配列の生成: 元の配列を逆順に走査してソートされた配列を生成するため、これも \(O(n)\) の時間がかかります。

したがって、分布数え上げソートの全体の時間計算量は次のようになります。

\[O(n + k)\]

ここで、\(n\) は入力データの数、\(k\) はデータの範囲(最大値)です。

空間計算量の解析

分布数え上げソートの空間計算量は、主に以下の要素から構成されます。

  1. カウント配列: カウント配列は、最大値 \(k\) に基づいて作成されるため、空間計算量は \(O(k)\) です。
  2. 出力配列: ソートされた結果を格納するための出力配列も必要で、これも \(O(n)\) の空間を使用します。

したがって、分布数え上げソートの全体の空間計算量は次のようになります。

\[O(n + k)\]

他のソートアルゴリズムとの比較

分布数え上げソートは、特定の条件下で非常に効率的ですが、他のソートアルゴリズムと比較すると、以下のような特徴があります。

ソートアルゴリズム時間計算量空間計算量特徴
分布数え上げソート\(O(n + k)\)\(O(n + k)\)整数の範囲が限られている場合に最適
クイックソート\(O(n \log n)\)\(O(\log n)\)平均的に高速だが、最悪の場合は遅い
マージソート\(O(n \log n)\)\(O(n)\)安定なソートだが、追加のメモリが必要
バブルソート\(O(n^2)\)\(O(1)\)非常に遅く、実用的ではない

分布数え上げソートは、特にデータの範囲が狭い場合に非常に効率的であり、他のソートアルゴリズムと比較しても優れた性能を発揮します。

しかし、データの範囲が広い場合には、他のアルゴリズムを選択する方が良いでしょう。

分布数え上げソートの応用例

大量の整数データのソート

分布数え上げソートは、大量の整数データを効率的にソートするために非常に適しています。

特に、データの範囲が限られている場合、例えば0から1000までの整数が大量にある場合、分布数え上げソートを使用することで、他のソートアルゴリズムよりも高速にソートを行うことができます。

この特性により、データベースや統計処理などの分野で広く利用されています。

制約のある範囲のデータのソート

分布数え上げソートは、データの範囲が明確に制約されている場合に特に効果的です。

例えば、特定の年齢層(0歳から100歳)やテストの点数(0点から100点)など、範囲が狭いデータを扱う際に、分布数え上げソートを使用することで、迅速にソートを行うことができます。

このような場合、他のソートアルゴリズムに比べて、計算時間を大幅に短縮できます。

負の数を含むデータのソート

分布数え上げソートは、負の数を含むデータのソートにも応用可能です。

負の数を扱うためには、カウント配列のインデックスを調整する必要があります。

具体的には、負の数の最小値を基準にしてカウント配列を作成し、すべての要素をその基準値でオフセットすることで、負の数を正しくカウントすることができます。

この方法により、負の数を含むデータセットでも効率的にソートが可能です。

文字列のソートへの応用

分布数え上げソートは、文字列のソートにも応用できます。

特に、文字列の各文字が特定の範囲(例えば、ASCIIコードの範囲)に収まる場合、分布数え上げソートを使用することで、文字列を効率的にソートすることができます。

例えば、固定長の文字列や、特定の文字セット(英数字など)を持つデータを扱う場合に、分布数え上げソートは非常に有効です。

このアプローチは、文字列のソートを行う際に、他のアルゴリズムよりも高速に処理を行うことができます。

まとめ

この記事では、分布数え上げソートの基本的な概念から実装手順、時間・空間計算量、応用例までを詳しく解説しました。

分布数え上げソートは、特に整数データのソートにおいて非常に効率的であり、特定の条件下で他のソートアルゴリズムよりも優れた性能を発揮します。

今後、データのソートが必要な際には、分布数え上げソートを検討し、その特性を活かして効率的なプログラムを実装してみてください。

関連記事

Back to top button