C++でバブルソートを解説!7選のコードで完全網羅

C++のバブルソートを学ぶための包括的ガイドC++
この記事は約20分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

C++でのプログラミングにおいて、バブルソートは初心者にも理解しやすく、また、上級者にとってもその内部動作の理解が重要なアルゴリズムです。

この記事では、バブルソートの基本的な概念から、さまざまな実装方法、応用例に至るまでを綿密に解説していきます。

プログラミングを学び始めたばかりの方から、すでにある程度の知識を持っている方まで、幅広くご利用いただける内容となっています。

C++におけるバブルソートの魅力を、具体的なコード例とともに探求していきましょう。

●バブルソートとは

バブルソートは、最も基本的なソーティングアルゴリズムの一つであり、その名の通り、「バブル(泡)」が水面に向かって上昇するように、配列の要素が順に並び替えられていきます。

このシンプルなアルゴリズムは、コードが理解しやすく、実装も比較的容易であるため、プログラミング初学者にとって理想的な学習素材となります。

しかし、効率面では他のソートアルゴリズムに劣る場合が多いため、実際のアプリケーションで使用されることは少ないですが、アルゴリズムの基本的な理解を深めるためには非常に有用です。

○バブルソートの基本概念

バブルソートは、隣接する要素を比較し、必要に応じて交換することで、リストや配列の要素を並び替える方法です。

具体的には、配列の先頭から開始し、隣接する各要素のペアを比較して、順序が逆であれば交換します。

このプロセスを配列の末尾まで繰り返し、次に再び配列の先頭から同様の操作を行います。

このプロセスは、配列の中の要素が正しい順序に並ぶまで繰り返されます。

バブルソートの名前は、小さい要素(軽い泡)が配列の「表面」(先頭)に「浮上」してくる様子に由来します。

○バブルソートのアルゴリズムの理解

バブルソートのアルゴリズムを理解する上で重要なのは、それがどのようにして配列の要素を順序通りに並び替えるかという点です。

一回の完全な配列の走査で、最も大きい要素(または最も小さい要素、ソート順に依存)が配列の適切な位置に「浮上」します。

このプロセスを配列の各要素が正しい位置に来るまで繰り返します。

バブルソートの特徴は、その実装の単純さにありますが、比較回数が多いため、効率的なソーティングアルゴリズムには通常匹敵しません。

しかし、コーディングの基本を学ぶには最適な例です。

バブルソートは、入れ替えが必要ないと判断されるまで、または特定の回数だけ実行されるまで、リスト全体または配列を繰り返し走査します。

要素の比較と交換が行われるたびに、少なくとも一つの要素がその最終的な位置に移動します。

これにより、繰り返しの各サイクルが終了するごとに、ソートされる要素の数が減少していきます。

このアルゴリズムの単純さは、理解と実装の容易さに反映されていますが、多数の要素を持つ配列やリストでは効率が低下します。

それでも、プログラミング初学者にとって、バブルソートはアルゴリズムの基本原則を理解するのに適した方法です。

●C++におけるバブルソートの実装

C++を使用してバブルソートを実装する際には、いくつかの重要なステップがあります。

バブルソートは、そのシンプルさから多くのプログラミング初心者にとって最初に学ぶソートアルゴリズムです。

ここでは、C++における基本的なバブルソートのコード例を紹介し、その後、より効率的なバリエーションについても見ていきます。

○サンプルコード1:基本的なバブルソート

バブルソートの基本的なアイデアは、配列内の隣接する要素を反復して比較し、必要に応じて交換することです。

ここでは、C++での基本的なバブルソートのサンプルコードを紹介します。

#include <iostream>
using namespace std;

void bubbleSort(int arr[], int n) {
    for (int i = 0; i < n-1; i++) {     
        for (int j = 0; j < n-i-1; j++) { 
            if (arr[j] > arr[j+1]) {
                swap(arr[j], arr[j+1]);
            }
        }
    }
}

int main() {
    int arr[] = {64, 34, 25, 12, 22, 11, 90};
    int n = sizeof(arr)/sizeof(arr[0]);
    bubbleSort(arr, n);
    cout << "Sorted array: \n";
    for (int i = 0; i < n; i++)
        cout << arr[i] << " ";
    cout << endl;
    return 0;
}

このコードでは、bubbleSort関数がバブルソートの主要な部分を担当しています。

二重のforループを使用して、配列内の要素を順番に比較し、必要に応じて交換しています。

このシンプルなアプローチは、バブルソートの基本的な概念を理解するのに適しています。

○サンプルコード2:最適化されたバブルソート

基本的なバブルソートは効率的ではありませんが、小さな改良によりそのパフォーマンスを向上させることができます。

下記のコードは、無駄な比較を避けるために最適化されたバブルソートを表しています。

void optimizedBubbleSort(int arr[], int n) {
    bool swapped;
    for (int i = 0; i < n-1; i++) {
        swapped = false;
        for (int j = 0; j < n-i-1; j++) {
            if (arr[j] > arr[j+1]) {
                swap(arr[j], arr[j+1]);
                swapped = true;
            }
        }
        // If no two elements were swapped by inner loop, then break
        if (!swapped)
            break;
    }
}

この最適化では、内側のループで一度も要素が交換されない場合、すでに配列がソートされていると判断し、ソートプロセスを早期に終了します。

これにより、すでにソートされている配列や、ほぼソートされた状態の配列に対して高い効率を発揮します。

○サンプルコード3:逆順のバブルソート

バブルソートは、昇順だけでなく降順のソートにも容易に適用できます。

下記のコードは、降順で配列をソートするバブルソートの実装例です。

void reverseBubbleSort(int arr[], int n) {
    for (int i = 0; i < n-1; i++) {     
        for (int j = 0; j < n-i-1; j++) { 
            if (arr[j] < arr[j+1]) {
                swap(arr[j], arr[j+1]);
            }
        }
    }
}

このバリエーションでは、比較条件を変更するだけで、簡単に降順のソートが可能になります。

これは、バブルソートの汎用性と柔軟性を表しています。

○サンプルコード4:バブルソートの変形(カクテルソート)

バブルソートの一種として、カクテルソート(またはシェーカーソート)があります。

このアルゴリズムは、従来のバブルソートを双方向に実行することで、配列の両端に対して効率よくソートを行うことができます。

#include <iostream>
using namespace std;

void cocktailSort(int arr[], int n) {
    bool swapped = true;
    int start = 0;
    int end = n - 1;

    while (swapped) {
        swapped = false;

        for (int i = start; i < end; ++i) {
            if (arr[i] > arr[i + 1]) {
                swap(arr[i], arr[i + 1]);
                swapped = true;
            }
        }

        if (!swapped)
            break;

        swapped = false;
        --end;

        for (int i = end - 1; i >= start; --i) {
            if (arr[i] > arr[i + 1]) {
                swap(arr[i], arr[i + 1]);
                swapped = true;
            }
        }

        ++start;
    }
}

int main() {
    int arr[] = {5, 3, 2, 8, 1, 4};
    int n = sizeof(arr) / sizeof(arr[0]);
    cocktailSort(arr, n);
    cout << "Sorted array: ";
    for (int i = 0; i < n; i++)
        cout << arr[i] << " ";
    cout << endl;
    return 0;
}

このコードでは、配列を前後から交互に走査していき、それぞれのパスで最大値と最小値を適切な位置に移動させています。

このアプローチにより、従来のバブルソートよりも高速に動作する場合があります。

○サンプルコード5:バブルソートを使用した複数のデータ型のソート

バブルソートは、異なるデータ型にも応用可能です。

下記の例では、C++のテンプレート機能を利用して、異なるデータ型でバブルソートを行う方法を表しています。

#include <iostream>
using namespace std;

template <typename T>
void bubbleSort(T arr[], int n) {
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                swap(arr[j], arr[j + 1]);
            }
        }
    }
}

int main() {
    int arr1[] = {64, 34, 25, 12, 22, 11, 90};
    int n1 = sizeof(arr1)/sizeof(arr1[0]);
    bubbleSort(arr1, n1);

    double arr2[] = {12.5, 24.8, 7.9, 15.3};
    int n2 = sizeof(arr2)/sizeof(arr2[0]);
    bubbleSort(arr2, n2);

    cout << "Sorted integer array: ";
    for (int i = 0; i < n1; i++)
        cout << arr1[i] << " ";
    cout << "\nSorted double array: ";
    for (int i = 0; i < n2; i++)
        cout << arr2[i] << " ";
    cout << endl;
    return 0;
}

このコードでは、テンプレート関数bubbleSortを定義しています。

これにより、整数や浮動小数点数など、さまざまなデータ型の配列に対して同じソート関数を使用できます。

テンプレートを使用することで、コードの汎用性が高まり、異なる状況に応じたソートの実装が可能になります。

●バブルソートの応用例

バブルソートは基本的なソートアルゴリズムであると同時に、その応用範囲は非常に広いです。

ここでは、バブルソートを応用したいくつかの実例を紹介し、その柔軟性と実用性を紹介します。

○サンプルコード6:バブルソートを使ったデータフィルタリング

バブルソートは、単純なソート処理のみならず、特定の条件に基づくデータのフィルタリングにも応用することができます。

たとえば、ある条件を満たす要素だけを配列の特定の位置に移動させることにバブルソートの原理を用いることが可能です。

#include <iostream>
using namespace std;

void filterSort(int arr[], int n, int filter) {
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - i - 1; j++) {
            if (arr[j] < filter) {
                swap(arr[j], arr[j + 1]);
            }
        }
    }
}

int main() {
    int arr[] = {5, 2, 9, 1, 5, 6};
    int n = sizeof(arr) / sizeof(arr[0]);
    int filter = 5;

    filterSort(arr, n, filter);
    cout << "Filtered array: ";
    for (int i = 0; i < n; i++)
        cout << arr[i] << " ";
    cout << endl;
    return 0;
}

このコードでは、filterSort関数を用いて、指定されたfilterよりも小さい要素を配列の右側に移動させています。

これにより、特定の条件に基づく要素のグルーピングが可能になります。

○サンプルコード7:バブルソートを活用したデータ解析

データ解析の分野でも、バブルソートは有効です。

たとえば、データセット内の要素をソートし、統計的な分析やビジュアライゼーションの前処理として使用することができます。

ここでは、C++を用いてデータセットをソートするサンプルコードを紹介します。

#include <iostream>
#include <vector>
using namespace std;

void sortData(vector<int>& data) {
    int n = data.size();
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - i - 1; j++) {
            if (data[j] > data[j + 1]) {
                swap(data[j], data[j + 1]);
            }
        }
    }
}

int main() {
    vector<int> data = {23, 45, 12, 37, 84, 56, 28};
    sortData(data);

    cout << "Sorted data: ";
    for (int i : data)
        cout << i << " ";
    cout << endl;
    return 0;
}

この例では、vector<int>型のデータをバブルソートを用いてソートしています。

データセットがソートされることにより、後続の分析作業が容易になり、データの理解が深まります。

バブルソートを活用することで、データ解析の効率と精度が向上します。

●注意点と対処法

バブルソートはその単純さゆえに多くの場面で使われますが、いくつかの重要な注意点があります。

これらを理解し、適切に対処することで、バブルソートのパフォーマンスを最大限に引き出すことが可能です。

○バブルソートの限界と対処法

バブルソートは基本的なアルゴリズムであり、簡単に実装できる反面、大きなデータセットに対しては非効率的です。

特に、要素数が多い場合や、元々の順序がランダムな場合には、多くの比較と交換が必要となり、実行時間が著しく長くなります。

これはバブルソートが平均および最悪の場合でO(n^2)の時間複雑度を持つことに起因します。

この限界に対処する方法の一つは、データセットのサイズや特性に応じて、より高効率のソートアルゴリズム(例えばクイックソートやマージソート)を選択することです。

しかし、バブルソートは比較的理解しやすく、小規模なデータセットや、ほぼ整列されたデータセットには有効であるため、これらの状況下では依然として有用です。

○バブルソート時のパフォーマンス改善

バブルソートのパフォーマンスを改善するための一つの方法は、不必要な比較を省略することです。

ここでは、最適化されたバブルソートのサンプルコードを紹介します。

このコードでは、一回の完全なループを経て要素の交換がなかった場合、配列がすでに整列されていると判断し、ソート処理を終了しています。

#include <iostream>
using namespace std;

void improvedBubbleSort(int arr[], int n) {
    for (int i = 0; i < n - 1; i++) {
        bool swapped = false;
        for (int j = 0; j < n - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                swap(arr[j], arr[j + 1]);
                swapped = true;
            }
        }
        if (!swapped) {
            break;
        }
    }
}

int main() {
    int arr[] = {32, 21, 45, 62, 7, 12};
    int n = sizeof(arr)/sizeof(arr[0]);
    improvedBubbleSort(arr, n);
    for (int i = 0; i < n; i++) {
        cout << arr[i] << " ";
    }
    cout << endl;
    return 0;
}

この改良により、すでに整列された配列に対しては、バブルソートのパフォーマンスが大幅に向上します。

しかし、大規模なデータセットやランダムな順序のデータセットに対しては、より効率的なアルゴリズムを検討することが重要です。

●バブルソートのカスタマイズ方法

バブルソートのアルゴリズムは、その基本的な形から様々な方法でカスタマイズすることができます。

これにより、特定の要件やデータの特性に適したソート処理を実現することが可能です。

○バブルソートのカスタム実装例

例えば、特定の基準でデータをソートしたい場合、比較条件をカスタマイズすることで、特定のニーズに合わせたソート処理を行うことができます。

ここでは、C++を用いたカスタム比較条件を持つバブルソートのサンプルコードを紹介します。

#include <iostream>
#include <vector>
#include <functional>
using namespace std;

void customBubbleSort(vector<int>& arr, function<bool(int, int)> compare) {
    int n = arr.size();
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - i - 1; j++) {
            if (compare(arr[j], arr[j + 1])) {
                swap(arr[j], arr[j + 1]);
            }
        }
    }
}

int main() {
    vector<int> arr = {5, 1, 4, 2, 8};
    // Custom sort: Sort by descending order
    customBubbleSort(arr, [](int a, int b) { return a < b; });

    cout << "Custom sorted array: ";
    for (int i : arr) {
        cout << i << " ";
    }
    cout << endl;
    return 0;
}

このコードでは、ラムダ式を使用して比較関数をカスタマイズし、降順でのソートを実行しています。

このように、比較条件を動的に変更することで、様々なソート基準に対応できる柔軟なバブルソートの実装が可能になります。

○異なるデータ構造でのバブルソート

バブルソートは、配列やリストのような標準的なデータ構造だけでなく、異なる種類のデータ構造にも適用することが可能です。

例えば、連結リストやツリー構造の要素に対してバブルソートを行う場合、アルゴリズムの基本原則は同じですが、要素の交換や比較の方法がデータ構造によって異なります。

struct ListNode {
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(nullptr) {}
};

void bubbleSortLinkedList(ListNode* head) {
    if (!head || !head->next) return;

    bool swapped;
    do {
        swapped = false;
        ListNode* current = head;
        while (current->next != nullptr) {
            if (current->val > current->next->val) {
                swap(current->val, current->next->val);
                swapped = true;
            }
            current = current->next;
        }
    } while (swapped);
}

// ListNodeのリストを作成し、bubbleSortLinkedList関数を用いてソートする

この例では、連結リストのノード間で値を交換することにより、バブルソートを適用しています。

異なるデータ構造にバブルソートを適用する際は、データ構造の特性を理解し、適切な比較と交換のメカニズムを実装することが重要です。

まとめ

C++でのバブルソートの解説を通して、このアルゴリズムの基本的な理解から応用、カスタマイズ方法に至るまで幅広く紹介しました。

バブルソートは、その単純さから入門者にも理解しやすく、小規模なデータセットや特定の条件下で非常に有用です。

また、様々なデータ構造に対応可能で、カスタマイズの柔軟性も高いことが分かります。

このガイドが、C++でのバブルソートの理解と実践に役立つことを願っています。