[C言語] リスト構造をマージソートする方法を解説
マージソートは、分割統治法を用いた効率的なソートアルゴリズムです。C言語でリスト構造をマージソートするには、まずリストを再帰的に分割し、各部分を個別にソートします。
その後、ソートされた部分をマージして一つのリストに統合します。リストの分割には、通常、リストの中間点を見つける方法が用いられます。
マージの際には、二つのリストの先頭要素を比較し、小さい方を新しいリストに追加する操作を繰り返します。これにより、リスト全体がソートされます。
C言語でのリスト構造の実装
リスト構造は、データを順序付けて格納するための基本的なデータ構造の一つです。
C言語では、ポインタを用いてリストを実装します。
ここでは、リスト構造の基本的な実装方法について解説します。
ノードの定義
リスト構造を実現するためには、まずノードを定義する必要があります。
ノードはデータと次のノードへのポインタを持つ構造体として定義します。
#include <stdio.h>
#include <stdlib.h>
// ノードの構造体定義
typedef struct Node {
    int data; // データ部分
    struct Node* next; // 次のノードへのポインタ
} Node;この構造体では、dataがノードに格納されるデータで、nextが次のノードを指すポインタです。
リストの初期化
リストを使用する前に、リストを初期化する必要があります。
初期化では、リストの先頭を指すポインタをNULLに設定します。
// リストの初期化
Node* initializeList() {
    return NULL; // 空のリストを表す
}この関数は、空のリストを表すNULLポインタを返します。
リストへの要素追加と削除
リストに要素を追加するには、新しいノードを作成し、リストの適切な位置に挿入します。
ここでは、リストの先頭に要素を追加する方法を示します。
// リストの先頭に要素を追加
void addElement(Node** head, int newData) {
    // 新しいノードの作成
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->data = newData;
    newNode->next = *head; // 現在の先頭を次のノードに設定
    *head = newNode; // 新しいノードを先頭に設定
}要素を削除するには、削除したい要素を見つけてリストから取り除きます。
ここでは、リストの先頭から要素を削除する方法を示します。
// リストの先頭から要素を削除
void deleteElement(Node** head) {
    if (*head == NULL) return; // リストが空の場合は何もしない
    Node* temp = *head; // 先頭ノードを一時保存
    *head = (*head)->next; // 先頭を次のノードに変更
    free(temp); // メモリを解放
}これらの関数を用いることで、リストに要素を追加したり削除したりすることができます。
リストの操作は、ポインタを用いることで効率的に行うことができます。
リスト構造をマージソートする手順
マージソートは、分割統治法を用いた効率的なソートアルゴリズムです。
リスト構造に対しても適用可能で、安定したソートを実現します。
ここでは、リスト構造をマージソートする手順について解説します。
リストを分割する方法
マージソートの最初のステップは、リストを分割することです。
リストを半分に分割するためには、2つのポインタを用いてリストを走査します。
// リストを2つに分割する関数
void splitList(Node* source, Node** frontRef, Node** backRef) {
    Node* fast;
    Node* slow;
    slow = source;
    fast = source->next;
    // fastがリストの終わりに到達するまで進める
    while (fast != NULL) {
        fast = fast->next;
        if (fast != NULL) {
            slow = slow->next;
            fast = fast->next;
        }
    }
    // slowはリストの中央に位置する
    *frontRef = source;
    *backRef = slow->next;
    slow->next = NULL;
}この関数では、slowとfastの2つのポインタを用いてリストを走査し、slowがリストの中央に到達した時点でリストを分割します。
分割したリストをマージする方法
分割したリストをマージするには、2つのリストを比較しながら新しいリストを作成します。
// 2つのリストをマージする関数
Node* mergeLists(Node* a, Node* b) {
    Node* result = NULL;
    // ベースケース
    if (a == NULL) return b;
    else if (b == NULL) return a;
    // aとbのデータを比較して小さい方を選択
    if (a->data <= b->data) {
        result = a;
        result->next = mergeLists(a->next, b);
    } else {
        result = b;
        result->next = mergeLists(a, b->next);
    }
    return result;
}この関数では、aとbのデータを比較し、小さい方のノードを新しいリストに追加していきます。
再帰的にマージソートを適用する
マージソートをリストに適用するには、リストを再帰的に分割し、マージしていきます。
// リストにマージソートを適用する関数
void mergeSort(Node** headRef) {
    Node* head = *headRef;
    Node* a;
    Node* b;
    // ベースケース: リストが空または1要素の場合
    if ((head == NULL) || (head->next == NULL)) {
        return;
    }
    // リストを2つに分割
    splitList(head, &a, &b);
    // 再帰的にそれぞれのリストをソート
    mergeSort(&a);
    mergeSort(&b);
    // ソートされたリストをマージ
    *headRef = mergeLists(a, b);
}この関数では、リストを分割し、再帰的にマージソートを適用していきます。
最終的に、ソートされたリストが得られます。
マージソートは、リスト構造においても効率的に動作し、安定したソートを提供します。
C言語でのマージソート実装例
ここでは、リスト構造に対するマージソートの実装例を示します。
各関数の役割と全体の流れを理解することで、効率的なソートアルゴリズムを実装できます。
コード全体の流れ
マージソートの実装は、以下のような流れで行います。
- リストを分割する
- 分割したリストを再帰的にソートする
- ソートされたリストをマージする
この流れを実現するために、分割関数、マージ関数、そしてメイン関数を実装します。
分割関数の実装
リストを2つに分割する関数を実装します。
この関数は、リストを前半と後半に分ける役割を持ちます。
// リストを2つに分割する関数
void splitList(Node* source, Node** frontRef, Node** backRef) {
    Node* fast;
    Node* slow;
    slow = source;
    fast = source->next;
    // fastがリストの終わりに到達するまで進める
    while (fast != NULL) {
        fast = fast->next;
        if (fast != NULL) {
            slow = slow->next;
            fast = fast->next;
        }
    }
    // slowはリストの中央に位置する
    *frontRef = source;
    *backRef = slow->next;
    slow->next = NULL;
}この関数では、slowとfastの2つのポインタを用いてリストを走査し、リストを前半と後半に分割します。
マージ関数の実装
分割されたリストをマージする関数を実装します。
この関数は、2つのソート済みリストを1つのソート済みリストに統合します。
// 2つのリストをマージする関数
Node* mergeLists(Node* a, Node* b) {
    Node* result = NULL;
    // ベースケース
    if (a == NULL) return b;
    else if (b == NULL) return a;
    // aとbのデータを比較して小さい方を選択
    if (a->data <= b->data) {
        result = a;
        result->next = mergeLists(a->next, b);
    } else {
        result = b;
        result->next = mergeLists(a, b->next);
    }
    return result;
}この関数では、aとbのデータを比較し、小さい方のノードを新しいリストに追加していきます。
メイン関数での動作確認
最後に、メイン関数でマージソートを実行し、動作を確認します。
int main() {
    Node* res = NULL;
    Node* a = NULL;
    // リストに要素を追加
    addElement(&a, 15);
    addElement(&a, 10);
    addElement(&a, 5);
    addElement(&a, 20);
    addElement(&a, 3);
    addElement(&a, 2);
    // マージソートを適用
    mergeSort(&a);
    // ソート結果を表示
    printf("Sorted Linked List is: \n");
    printList(a);
    return 0;
}このメイン関数では、リストに要素を追加し、mergeSort関数を呼び出してリストをソートします。
printList関数を用いて、ソートされたリストを表示します。
Sorted Linked List is: 
2 3 5 10 15 20この実行例では、リストが昇順にソートされていることが確認できます。
マージソートは、リスト構造においても効率的に動作し、安定したソートを提供します。
マージソートの応用例
マージソートは、その安定性と効率性から、さまざまな分野で応用されています。
ここでは、マージソートの具体的な応用例をいくつか紹介します。
大規模データのソート
マージソートは、外部ソートアルゴリズムとして大規模データのソートに適しています。
特に、メモリに収まりきらないデータをソートする際に有効です。
マージソートは、データを小さなチャンクに分割し、それぞれをソートした後にマージするため、ディスクI/Oを効率的に利用できます。
- 利点: メモリ使用量が一定で、ディスク上のデータを効率的に処理可能
- 適用例: 大規模なログファイルのソート、ビッグデータ処理
リアルタイムシステムでの利用
リアルタイムシステムでは、ソートの安定性と予測可能な実行時間が重要です。
マージソートは、最悪計算量がO(n log n)であり、安定なソートを提供するため、リアルタイムシステムでの利用に適しています。
- 利点: 安定性が高く、予測可能な実行時間
- 適用例: リアルタイムデータ処理、金融取引システム
データベースのインデックス作成
データベースでは、インデックスを作成する際にデータをソートする必要があります。
マージソートは、安定性と効率性から、インデックス作成においても利用されます。
特に、データベースの更新が頻繁に行われる場合、安定なソートが求められます。
- 利点: 安定なソートにより、同じキーを持つデータの順序が保持される
- 適用例: データベースのクエリ最適化、インデックス再構築
これらの応用例からもわかるように、マージソートはさまざまな場面でその特性を活かして利用されています。
特に、安定性と効率性が求められる場面での利用が多く見られます。
まとめ
マージソートは、安定性と効率性を兼ね備えたソートアルゴリズムであり、リスト構造においてもその特性を活かして利用できます。
この記事では、リスト構造に対するマージソートの実装方法や応用例について詳しく解説しました。
これを機に、実際のプログラミングでマージソートを活用し、データ処理の効率化を図ってみてください。
 
![[C言語] ドラゴン曲線を計算するプログラムを実装する方法](https://af-e.net/wp-content/uploads/2024/09/thumbnail-44015.png)
![[C言語] トポロジカルソートを実装する方法](https://af-e.net/wp-content/uploads/2024/09/thumbnail-44014.png)
![[C言語] ハノイの塔を解くプログラムの作成方法](https://af-e.net/wp-content/uploads/2024/09/thumbnail-44019.png)
![[C言語] はさみうち法(非線形方程式)を実装する方法](https://af-e.net/wp-content/uploads/2024/09/thumbnail-44017.png)
![[C言語] ナップザック問題を動的計画法で解く方法](https://af-e.net/wp-content/uploads/2024/09/thumbnail-44016.png)
![[C言語] フラクタル圧縮を用いた画像圧縮方法](https://af-e.net/wp-content/uploads/2024/09/thumbnail-44023.png)
![[C言語] プサイ関数/ポリガンマ関数を実装する方法](https://af-e.net/wp-content/uploads/2024/09/thumbnail-44022.png)
![[C言語] フィボナッチ探索を実装する方法](https://af-e.net/wp-content/uploads/2024/09/thumbnail-44021.png)
![[C言語] フィボナッチ数列を求めるアルゴリズムの書き方](https://af-e.net/wp-content/uploads/2024/09/thumbnail-44020.png)
![[C言語] ベルマンフォード法を実装する方法](https://af-e.net/wp-content/uploads/2024/09/thumbnail-44029.png)
![[C言語] ベータ分布を計算して乱数を生成する方法](https://af-e.net/wp-content/uploads/2024/09/thumbnail-44028.png)
![[C言語] ベータ関数を実装する方法](https://af-e.net/wp-content/uploads/2024/09/thumbnail-44027.png)