アルゴリズム日記 - Bubble Sort
著者 WaitZ
Sorting AlgorithmsbeginnerBubble SortO(n^2)O(1)algorithmsbubble-sortbeginner
今日のトピック (Today's Topic)
トピック: Bubble Sort
難易度: beginner
カテゴリー: Sorting Algorithms
時間計算量: O(n^2)
空間計算量: O(1)
概念学習 (Concept Learning)
核心概念
**バブルソート(Bubble Sort)**は、最もシンプルなソートアルゴリズムの1つです。配列を繰り返し走査し、隣接する要素を比較して、順序が間違っている場合は交換します。このアルゴリズムは、小さい要素が配列の先頭に「泡のように浮かび上がり」、大きい要素が末尾に「沈む」ことからこの名前が付けられました。
アルゴリズムは、交換が不要になるまで配列を繰り返し走査します。これは配列がソートされたことを示します。各完全な走査により、最大の未ソート要素が末尾の正しい位置に移動することが保証されます。
重要ポイント
- 比較ベース: バブルソートは隣接する要素のペアを比較します
- 安定: 等しい要素はソート後も相対的な順序を維持します
- インプレース: O(1)の追加メモリスペースのみが必要です
- シンプルだが非効率: 理解と実装は簡単ですが、大規模なデータセットでは性能が悪いです
- 適応的: 配列が既にソートされているかを検出するように最適化できます
アルゴリズムステップ
- 最初から開始: 配列の最初の要素から始めます
- 隣接ペアを比較: 隣接する要素のペア(arr[i]とarr[i+1])を比較します
- 必要に応じて交換: 要素が間違った順序の場合(arr[i] > arr[i+1])、交換します
- 末尾まで続ける: 次のペアに移動し、配列の末尾に達するまで繰り返します
- パスを繰り返す: 各完全なパスの後、最大の要素が正しい位置に配置されます
- 範囲を縮小: 後続の各パスで、最後のソート済み要素を無視します
- 完了をチェック: パスで交換が発生しなかった場合、配列はソート済みで停止できます
実装 (Implementation)
JavaScript実装
function bubbleSort(arr) {
const n = arr.length;
// 外側のループ:パスの回数
for (let i = 0; i < n - 1; i++) {
// 内側のループ:隣接要素の比較
for (let j = 0; j < n - i - 1; j++) {
// 要素が間違った順序の場合は交換
if (arr[j] > arr[j + 1]) {
// 分割代入を使用して交換
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
}
}
}
return arr;
}
// 使用例
const array = [64, 34, 25, 12, 22, 11, 90];
console.log("元の配列:", array);
console.log("ソート済み:", bubbleSort([...array]));
// 出力: ソート済み: [11, 12, 22, 25, 34, 64, 90]
最適化版(早期終了)
function bubbleSortOptimized(arr) {
const n = arr.length;
for (let i = 0; i < n - 1; i++) {
let swapped = false;
for (let j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
swapped = true;
}
}
// 交換が発生しなかった場合、配列は既にソート済み
if (!swapped) break;
}
return arr;
}
Kotlin実装
fun bubbleSort(arr: IntArray): IntArray {
val n = arr.size
for (i in 0 until n - 1) {
var swapped = false
for (j in 0 until n - i - 1) {
if (arr[j] > arr[j + 1]) {
// 要素を交換
val temp = arr[j]
arr[j] = arr[j + 1]
arr[j + 1] = temp
swapped = true
}
}
// 既にソート済みの場合は早期終了
if (!swapped) break
}
return arr
}
// 使用例
fun main() {
val array = intArrayOf(64, 34, 25, 12, 22, 11, 90)
println("元の配列: ${array.contentToString()}")
bubbleSort(array)
println("ソート済み: ${array.contentToString()}")
}
説明
- 解法の核心概念: 配列全体がソートされるまで、隣接要素を繰り返し比較して交換します
- 時間計算量:
- 最良の場合: O(n) - 配列が既にソート済みの場合(最適化あり)
- 平均の場合: O(n²) - 配列を複数回走査する必要があります
- 最悪の場合: O(n²) - 配列が逆順にソートされている場合
- 空間計算量: O(1) - 一時変数用の定数量の追加スペースのみを使用します
- エッジケースと一般的な誤り:
- 空の配列または単一要素: 既にソート済み、そのまま返します
- すべての要素が同一: 交換は不要、最適化により早期終了します
- 内側のループ範囲を減らすこと(n - i - 1)を忘れると、既にソート済みの要素の比較が無駄になります
- 最適化フラグを実装しないと、不要な反復が発生する可能性があります
練習問題 (Practice Problems)
問題リスト
LeetCode 912 - Sort an Array
- 難易度: Medium
- バブルソートを適用して整数配列をソートします
- 注: この問題には最適ではありませんが、練習には良いです
基本的な実装
- 配列 [5, 2, 8, 1, 9] をバブルソートでソートします
- 行われた交換の回数をカウントします
カスタム比較関数を使用したソート
- バブルソートを使用して、文字列の配列を長さでソートします
- 例: ["apple", "pie", "banana", "cat"] → ["pie", "cat", "apple", "banana"]
最適化チャレンジ
- 最適化版を実装してテストします
- 既にソートされた配列と逆順にソートされた配列での反復を比較します
バブルソートの可視化
- 各パス後の配列の状態を出力します
- 要素がどのように位置に「泡立つ」かを観察します
解法ノート
重要点:
- インデックスの範囲外エラーを避けるため、常に配列の境界をチェックします
swappedフラグを使った最適化は、ほぼソートされた配列のパフォーマンスを大幅に向上させます- 各パスで少なくとも1つの要素が最終位置に移動することを覚えておきます
落とし穴:
- 内側のループの境界を減らすこと(
n - i - 1を使用)を忘れる - 空の配列や単一要素配列などのエッジケースを処理しない
- 大規模なデータセットにバブルソートを使用しようとする(代わりにクイックソートやマージソートを使用)
ベストプラクティス:
- 常に早期終了を伴う最適化版を実装します
- バブルソートは小規模なデータセット(< 100要素)または教育目的にのみ使用します
- 安定性の要件を考慮します:バブルソートは等しい要素の相対的な順序を維持します
重要な詳細 (Important Details)
注意すべきエッジケース
- 空の配列
[]: そのまま返します - 単一要素
[5]: 既にソート済み - 既にソート済み
[1, 2, 3, 4]: 最適化版は最初のパス後に終了します - 逆順にソート済み
[4, 3, 2, 1]: 最悪の場合、最大の交換が必要です - すべて重複
[5, 5, 5, 5]: 交換は不要、早期終了します
適用シナリオと制限
使用すべき場合:
- 教育目的(ソートアルゴリズムの学習)
- 非常に小規模なデータセット(< 10-20要素)
- ほぼソート済みの配列(最適化あり)
- 効率性よりもシンプルさが重要な場合
- 安定したソートが必要で、データセットが小さい場合
制限:
- 性能が悪い: O(n²)の時間計算量は大規模なデータセットには遅すぎます
- 実用的でない: 本番環境では、より良いアルゴリズム(マージソート、クイックソート、ヒープソート)が存在します
- 多くの比較: 最適化版でも多くの比較が必要です
拡張アプリケーションやバリエーション
双方向バブルソート(カクテルソート)
- 前方と後方のパスを交互に行います
- 場合によっては少し効率的です
フラグ付きバブルソート
- 交換が発生したかを追跡して早期終了を可能にします
- 最良の場合の時間計算量をO(n)に改善します
交換のカウント
- 最小操作に関する面接の質問に役立ちます
- 配列の「ソート済み度」を分析するのに役立ちます
異なるデータ型のソート
- オブジェクト、文字列、カスタム型用の比較関数を適応させます
- 比較関数の使用を練習します
今日の振り返り (Daily Reflection)
重要な収穫: バブルソートは比較ベースのソートの基礎を教える基本的なアルゴリズムです。大規模なデータセットには効率的ではありませんが、アルゴリズム分析、ループ不変条件、最適化技術への優れた入門です。バブルソートを理解することは、より洗練されたソートアルゴリズムを評価するのに役立ちます。
関連アルゴリズム:
- 選択ソート: 類似のO(n²)計算量ですが、異なるアプローチ(各パスで最小値を見つける)を使用します
- 挿入ソート: O(n²)ですが、小規模/ほぼソート済みの配列では実際にはより効率的です
- カクテルソート: バブルソートの双方向バリアント
- マージソート: 効率的なO(n log n)の分割統治ソート
- クイックソート: 平均的な場合O(n log n)の人気のあるソートアルゴリズム
- ヒープソート: ヒープデータ構造を使用したO(n log n)のソート