ソート
ソート(Sorting)とは、一連のデータを特定の順序に並べ替えるプロセスであり、データ処理において最も基礎的かつ最も研究が進んでいるアルゴリズム問題の一つです。
ソートアルゴリズム一覧
ソートアルゴリズム一覧 — 時間計算量と特徴の比較
基本ソート(O(n²))
バブルソート、選択ソート、挿入ソートは実装が簡単で、小規模データや教育用の理解に適しています。
高度なソート(O(n log n))
クイックソート、マージソート、ヒープソートは実務で最も広く使われています。シェルソートは挿入ソートの改良版です。
実例
#include <stdio.h>
void swap(int* a, int* b) { int t = *a; *a = *b; *b = t; }
/* バブルソート — O(n²)、安定
各ラウンドで最大要素を末尾に「バブル」させる */
void bubbleSort(int arr[], int n) {
for (int i = 0; i < n - 1; i++) {
int swapped = 0; /* 最適化:早期終了 */
for (int j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
swap(&arr[j], &arr[j + 1]);
swapped = 1;
}
}
if (!swapped) break; /* 整列済みのため、早期終了 */
}
}
/* クイックソートの分割関数 — 平均O(n log n)、不安定
基準pivotを選び、pivotより小さいものを左、大きいものを右に置く */
int partition(int arr[], int low, int high) {
int pivot = arr[high]; /* 最後の要素を基準として選ぶ */
int i = low - 1; /* i は pivot より小さい領域の末尾を指す */
for (int j = low; j < high; j++) {
if (arr[j] < pivot) {
i++;
swap(&arr[i], &arr[j]);
}
}
swap(&arr[i + 1], &arr[high]); /* 基準を正しい位置に置く */
return i + 1;
}
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); /* 右半分を再帰的にソート */
}
}
void printArray(int arr[], int n) {
for (int i = 0; i < n; i++) printf("%d ", arr[i]);
printf("\n");
}
int main() {
int arr1[] = {64, 34, 25, 12, 22, 11, 90};
int n1 = 7;
bubbleSort(arr1, n1);
printf("バブルソート: "); printArray(arr1, n1);
/* 出力: 11 12 22 25 34 64 90 */
int arr2[] = {64, 34, 25, 12, 22, 11, 90};
quickSort(arr2, 0, 6);
printf("クイックソート: "); printArray(arr2, 7);
/* 出力: 11 12 22 25 34 64 90 */
return 0;
}
void swap(int* a, int* b) { int t = *a; *a = *b; *b = t; }
/* バブルソート — O(n²)、安定
各ラウンドで最大要素を末尾に「バブル」させる */
void bubbleSort(int arr[], int n) {
for (int i = 0; i < n - 1; i++) {
int swapped = 0; /* 最適化:早期終了 */
for (int j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
swap(&arr[j], &arr[j + 1]);
swapped = 1;
}
}
if (!swapped) break; /* 整列済みのため、早期終了 */
}
}
/* クイックソートの分割関数 — 平均O(n log n)、不安定
基準pivotを選び、pivotより小さいものを左、大きいものを右に置く */
int partition(int arr[], int low, int high) {
int pivot = arr[high]; /* 最後の要素を基準として選ぶ */
int i = low - 1; /* i は pivot より小さい領域の末尾を指す */
for (int j = low; j < high; j++) {
if (arr[j] < pivot) {
i++;
swap(&arr[i], &arr[j]);
}
}
swap(&arr[i + 1], &arr[high]); /* 基準を正しい位置に置く */
return i + 1;
}
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); /* 右半分を再帰的にソート */
}
}
void printArray(int arr[], int n) {
for (int i = 0; i < n; i++) printf("%d ", arr[i]);
printf("\n");
}
int main() {
int arr1[] = {64, 34, 25, 12, 22, 11, 90};
int n1 = 7;
bubbleSort(arr1, n1);
printf("バブルソート: "); printArray(arr1, n1);
/* 出力: 11 12 22 25 34 64 90 */
int arr2[] = {64, 34, 25, 12, 22, 11, 90};
quickSort(arr2, 0, 6);
printf("クイックソート: "); printArray(arr2, 7);
/* 出力: 11 12 22 25 34 64 90 */
return 0;
}
分割統治の概念図
三大O(n log n)ソートアルゴリズム — 分割統治戦略の比較
クイックソート(分割)
基準pivotを選ぶ
基準より小さい→左側、大きい→右側
左右の部分配列を再帰的にソート
不安定 | インプレース | 最も一般的
マージソート(統合)
再帰的に2分割して最小単位まで
2つの整列済み部分列を統合
追加配列が必要 O(n)
安定 | 追加空間が必要 | 外部ソート
ヒープソート(ヒープ化)
最大ヒープを構築 O(n)
ヒープの先頭(最大)を末尾に置く
ヒープを再調整 O(log n)
不安定 | インプレース O(1) | 空間効率が最良
ソートアルゴリズム完全比較
| アルゴリズム | 最良 | 平均 | 最悪 | 空間 | 安定性 |
|---|---|---|---|---|---|
| バブルソート | O(n) | O(n²) | O(n²) | O(1) | 安定 |
| 選択ソート | O(n²) | O(n²) | O(n²) | O(1) | 不安定 |
| 挿入ソート | O(n) | O(n²) | O(n²) | O(1) | 安定 |
| クイックソート | O(n log n) | O(n log n) | O(n²) | O(log n) | 不安定 |
| マージソート | O(n log n) | O(n log n) | O(n log n) | O(n) | 安定 |
| ヒープソート | O(n log n) | O(n log n) | O(n log n) | O(1) | 不安定 |
| シェルソート | O(n log n) | O(n^1.3) | O(n²) | O(1) | 不安定 |
その他の拡張実際の選定アドバイス:一般的な用途ではクイックソートを第一選択に。安定性が必要な場合はマージソート、メモリが極端に制限されている場合はヒープソート、小規模またはほぼ整列済みのデータには挿入ソートを選びましょう。