単方向連結リストの基本操作とC言語による実装

連続しないメモリ領域にデータを格納するため、順序付きリスト(配列)の挿入・削除における線形コストを回避する方法として、連結リストが用いられる。 連結リストは、メモリ上で連続しない複数の構造体から構成され、各構造体はデータ要素と次の要素へのポインタ(next)を持つ。末尾の要素のnextはNULLとなる。 単方向連結リストは、各ノードが1つのポインタのみを持つリストである。

特徴:

  • ノードのメモリ上の位置は任意であり、論理的に隣接するデータが物理的にも隣接するとは限らない。
  • アクセスは先頭ポインタ(head)を介してのみ行われ、各ノードのnextポインタを順にたどる。

1. 単方向連結リストの定義

以下では、先頭ノード(ダミーノード)を持つ単方向連結リストを扱う。

1.1 ヘッダファイルと構造体

#include <stdio.h>
#include <stdlib.h>

typedef struct SNode {
    int data;
    struct SNode* next;
} SNode, *SinglyLinkedList;

1.2 初期化

初期化関数では、先頭ポインタのポインタを受け取り、ダミーノードを生成する。 SinglyLinkedList* L とすることで、関数内部で実際の先頭ポインタを変更できる。

void initializeList(SinglyLinkedList* L) {
    *L = (SinglyLinkedList)malloc(sizeof(SNode));
    if (*L == NULL) {
        return;
    }
    (*L)->next = NULL;
}

2. リストの生成

2.1 末尾挿入による生成

void createListTail(SinglyLinkedList L) {
    int val, count = 2;
    SinglyLinkedList newNode, tail = L;
    printf("最初のデータを入力('.'で終了): ");
    while (scanf("%d", &val)) {
        newNode = (SinglyLinkedList)malloc(sizeof(SNode));
        newNode->data = val;
        newNode->next = NULL;
        tail->next = newNode;
        tail = newNode;
        printf("第%dデータを入力('.'で終了): ", count++);
    }
    printf("入力完了\n");
    // 入力バッファをクリア
    int ch;
    while ((ch = getchar()) != '\n' && ch != EOF);
}

2.2 先頭挿入による生成

void createListHead(SinglyLinkedList L) {
    int val, count = 2;
    SinglyLinkedList newNode;
    printf("最初のデータを入力('.'で終了): ");
    while (scanf("%d", &val)) {
        newNode = (SinglyLinkedList)malloc(sizeof(SNode));
        newNode->data = val;
        newNode->next = L->next;
        L->next = newNode;
        printf("第%dデータを入力('.'で終了): ", count++);
    }
    printf("入力完了\n");
    int ch;
    while ((ch = getchar()) != '\n' && ch != EOF);
}

3. 基本操作

3.1 長さ取得

int getListLength(SinglyLinkedList L) {
    int len = 0;
    SinglyLinkedList p = L->next;
    while (p != NULL) {
        len++;
        p = p->next;
    }
    return len;
}

3.2 空チェック

int isEmpty(SinglyLinkedList L) {
    return (L->next == NULL);
}

3.3 インデックスによる要素取得

void getElementByIndex(SinglyLinkedList L) {
    int idx, pos = 0;
    SinglyLinkedList p = L;
    printf("取得したい要素の番号を入力: ");
    scanf("%d", &idx);
    while (p != NULL && pos < idx) {
        p = p->next;
        pos++;
    }
    if (p == NULL || idx == 0) {
        printf("第%d要素は存在しません\n", idx);
    } else {
        printf("第%d要素の値: %d\n", idx, p->data);
    }
}

3.4 値による検索

void locateElementByValue(SinglyLinkedList L) {
    int target, pos = 1;
    SinglyLinkedList p = L->next;
    printf("検索する値を入力: ");
    scanf("%d", &target);
    while (p != NULL && p->data != target) {
        p = p->next;
        pos++;
    }
    if (p) {
        printf("値 %d は第%d要素に見つかりました(アドレス: %p)\n", target, pos, (void*)p);
    } else {
        printf("値 %d は見つかりませんでした\n", target);
    }
}

3.5 挿入

void insertElement(SinglyLinkedList L) {
    SinglyLinkedList p = L, newNode;
    int idx, pos = 0, val;
    printf("挿入位置(1始まり): ");
    scanf("%d", &idx);
    printf("挿入する値: ");
    scanf("%d", &val);
    while (p != NULL && pos < idx - 1) {
        p = p->next;
        pos++;
    }
    if (p == NULL || pos > idx - 1) {
        printf("挿入位置が不正です\n");
        return;
    }
    newNode = (SinglyLinkedList)malloc(sizeof(SNode));
    newNode->data = val;
    newNode->next = p->next;
    p->next = newNode;
    printf("挿入成功\n");
}

3.6 削除

void deleteElement(SinglyLinkedList L) {
    SinglyLinkedList p = L, q;
    int idx, pos = 0;
    printf("削除する要素の位置: ");
    scanf("%d", &idx);
    while (p != NULL && pos < idx - 1) {
        p = p->next;
        pos++;
    }
    if (p == NULL || pos > idx - 1 || (p->next == NULL && idx == 1)) {
        printf("削除位置が不正です\n");
        return;
    }
    q = p->next;
    p->next = q->next;
    printf("削除された値: %d\n", q->data);
    free(q);
}

3.7 表示

void printList(SinglyLinkedList L) {
    SinglyLinkedList p = L->next;
    if (p == NULL) {
        printf("リストは空です\n");
        return;
    }
    printf("リスト: ");
    while (p != NULL) {
        printf("%d", p->data);
        if (p->next != NULL) printf(" -> ");
        p = p->next;
    }
    printf("\n");
}

3.8 クリア(全要素削除)

void clearList(SinglyLinkedList L) {
    SinglyLinkedList p = L->next, q;
    while (p != NULL) {
        q = p;
        p = p->next;
        free(q);
    }
    L->next = NULL;
    printf("リストをクリアしました\n");
}

3.9 破棄(ヘッダノードも解放)

void destroyList(SinglyLinkedList* L) {
    SinglyLinkedList p = *L, q;
    while (p != NULL) {
        q = p;
        p = p->next;
        free(q);
    }
    *L = NULL;
    printf("リストを破棄しました\n");
}

4. メニューとメイン関数

void showMenu() {
    printf("================================================\n");
    printf(" 1: 末尾挿入でリスト生成\n");
    printf(" 2: 先頭挿入でリスト生成\n");
    printf(" 3: 長さ取得\n");
    printf(" 4: 空チェック\n");
    printf(" 5: インデックスで要素取得\n");
    printf(" 6: 値で検索\n");
    printf(" 7: 挿入\n");
    printf(" 8: 表示\n");
    printf(" 9: 削除\n");
    printf("10: クリア\n");
    printf("11: 破棄\n");
    printf("12: 終了\n");
    printf("================================================\n");
}

int main() {
    showMenu();
    SinglyLinkedList list = NULL;
    initializeList(&list);
    int choice, ch;
    printf("操作番号を入力: ");
    while (scanf("%d", &choice)) {
        if (choice == 12) {
            printf("プログラムを終了します\n");
            destroyList(&list);
            break;
        }
        switch (choice) {
            case 1: createListTail(list); break;
            case 2: createListHead(list); break;
            case 3: printf("長さ: %d\n", getListLength(list)); break;
            case 4: printf(isEmpty(list) ? "空です\n" : "空ではありません\n"); break;
            case 5: getElementByIndex(list); break;
            case 6: locateElementByValue(list); break;
            case 7: insertElement(list); break;
            case 8: printList(list); break;
            case 9: deleteElement(list); break;
            case 10: clearList(list); break;
            case 11: destroyList(&list); 
                     printf("リストを破棄後、再初期化します\n");
                     initializeList(&list);
                     break;
            default: printf("不正な入力です\n");
        }
        // 入力バッファクリア(scanf後に残る改行など)
        while ((ch = getchar()) != '\n' && ch != EOF);
        printf("操作番号を入力: ");
    }
    return 0;
}

タグ: C言語 単方向連結リスト データ構造 リンクリスト 動的メモリ確保

7月12日 21:04 投稿