【C言語】再帰関数でクイックソートを実装する

この記事では、C言語を使ってクイックソートという効率的なソートアルゴリズムを実装する方法を学びます。

クイックソートは、データを素早く並べ替えるための手法で、特に大きなデータセットに対して効果的です。

この記事を通じて、クイックソートの基本的な考え方や、実際のコードを見ながらその仕組みを理解できるようになります。

目次から探す

クイックソートのアルゴリズム

クイックソートは、効率的なソートアルゴリズムの一つで、特に大規模なデータセットに対して非常に高速に動作します。

このアルゴリズムは、分割統治法に基づいており、データを再帰的に処理することでソートを実現します。

ここでは、クイックソートの基本概念や手順について詳しく解説します。

クイックソートの基本概念

クイックソートは、配列をソートするためのアルゴリズムで、以下のような特徴があります。

  • 効率性: 平均的な時間計算量はO(n log n)であり、最悪の場合でもO(n^2)ですが、適切なピボット選択を行うことでこの最悪ケースを避けることができます。
  • インプレースソート: 追加のメモリをほとんど使用せずにソートを行うため、メモリ効率が良いです。
  • 再帰的アプローチ: 配列を小さな部分に分割し、それぞれを再帰的にソートします。

分割統治法の説明

分割統治法は、問題を小さな部分に分割し、それぞれを解決した後に結果を統合する手法です。

クイックソートでは、以下の3つのステップでこの手法を実現します。

  1. 分割: 配列からピボットを選び、ピボットより小さい要素と大きい要素に分けます。
  2. 再帰的ソート: 分割された部分配列に対して再帰的にクイックソートを適用します。
  3. 統合: 分割された部分配列がソートされると、全体の配列もソートされます。

このプロセスにより、クイックソートは効率的にデータを整理します。

ピボットの選択方法

ピボットの選択は、クイックソートの性能に大きな影響を与えます。

一般的なピボット選択方法には以下のようなものがあります。

  • 最初の要素: 配列の最初の要素をピボットとして選択します。
  • 最後の要素: 配列の最後の要素をピボットとして選択します。
  • 中央値: 配列の中央値をピボットとして選択します。

これにより、分割が均等になりやすく、最悪ケースを避けることができます。

ピボットの選択方法によって、アルゴリズムの効率が変わるため、適切な方法を選ぶことが重要です。

クイックソートの手順

クイックソートの手順は以下のようになります。

  1. 配列からピボットを選択します。
  2. 配列をピボットを基準にして2つの部分に分割します。

左側にはピボットより小さい要素、右側にはピボットより大きい要素を配置します。

  1. 左側と右側の部分配列に対して再帰的にクイックソートを適用します。
  2. 基本ケースに達した場合(配列のサイズが1以下)、その配列はすでにソートされています。

この手順を繰り返すことで、最終的に全体の配列がソートされます。

クイックソートはその効率性とシンプルさから、実際のプログラミングにおいても広く使用されています。

C言語でのクイックソートの実装

クイックソートは、効率的なソートアルゴリズムの一つで、再帰的に配列を分割していくことで整列を行います。

ここでは、C言語を用いてクイックソートを実装する方法を詳しく解説します。

必要なヘッダファイル

C言語でクイックソートを実装するためには、標準入出力を扱うためのヘッダファイルが必要です。

以下のように、stdio.hをインクルードします。

#include <stdio.h>

標準ライブラリのインクルード

クイックソートの実装には、配列の操作や入出力を行うために、標準ライブラリを使用します。

以下のように、必要なライブラリをインクルードします。

#include <stdio.h>
#include <stdlib.h> // 動的メモリ確保のため

クイックソート関数の実装

クイックソートの実装は、主に以下の部分から構成されます。

関数のシグネチャ

クイックソートを実装するための関数のシグネチャは以下のようになります。

この関数は、配列、配列の最初のインデックス、最後のインデックスを引数として受け取ります。

void quickSort(int arr[], int low, int high);

ピボット選択の実装

ピボットは、配列を分割する基準となる要素です。

ここでは、配列の最後の要素をピボットとして選択する方法を示します。

int partition(int arr[], int low, int high) {
    int pivot = arr[high]; // ピボットを配列の最後の要素に設定
    int i = (low - 1); // 小さい要素のインデックス
    for (int j = low; j < high; j++) {
        if (arr[j] < pivot) { // ピボットより小さい要素を見つけた場合
            i++; // 小さい要素のインデックスを増加
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp; // 要素を交換
        }
    }
    // ピボットを正しい位置に移動
    int temp = arr[i + 1];
    arr[i + 1] = arr[high];
    arr[high] = temp;
    return (i + 1); // ピボットのインデックスを返す
}

配列の分割処理

配列を分割するために、partition関数を呼び出します。

この関数は、ピボットを基準にして配列を二つの部分に分け、ピボットの最終的な位置を返します。

再帰的呼び出しの実装

クイックソートの再帰的な呼び出しは、分割された配列に対して再度クイックソートを適用することで行います。

以下のように実装します。

void quickSort(int arr[], int low, int high) {
    if (low < high) {
        int pi = partition(arr, low, high); // ピボットのインデックスを取得
        // ピボットの左側と右側の部分配列に対して再帰的にクイックソートを適用
        quickSort(arr, low, pi - 1);
        quickSort(arr, pi + 1, high);
    }
}

基本ケースの設定

再帰関数には基本ケースが必要です。

ここでは、lowhigh以上の場合、すなわち配列の要素が1つ以下の場合には何もせずに戻るように設定しています。

完成したコード

以下に、クイックソートの全体のコードを示します。

#include <stdio.h>
#include <stdlib.h>
void quickSort(int arr[], int low, int high);
int partition(int arr[], int low, int high);

int main() {
    int arr[] = {10, 7, 8, 9, 1, 5};
    int n = sizeof(arr) / sizeof(arr[0]);
    quickSort(arr, 0, n - 1);
    printf("ソートされた配列: \n");
    for (int i = 0; i < n; i++)
        printf("%d ", arr[i]);
    printf("\n");
    return 0;
}
void quickSort(int arr[], int low, int high) {
    if (low < high) {
        int pi = partition(arr, low, high);
        quickSort(arr, low, pi - 1);
        quickSort(arr, pi + 1, high);
    }
}
int partition(int arr[], int low, int high) {
    int pivot = arr[high];
    int i = (low - 1);
    for (int j = low; j < high; j++) {
        if (arr[j] < pivot) {
            i++;
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
    }
    int temp = arr[i + 1];
    arr[i + 1] = arr[high];
    arr[high] = temp;
    return (i + 1);
}

このコードを実行すると、配列がソートされ、結果が表示されます。

クイックソートは、平均的な時間計算量がO(n log n)であり、非常に効率的なソートアルゴリズムです。

目次から探す