C言語で連鎖リストの実装

連鎖リストの概念と構造

連鎖リストは物理的に非連続で順序が保たれていないデータ構造であり、論理的な順序は各ノード間のポインタリンクによって実現されます。

連鎖リストの分類

実際のアプリケーションでは様々な種類の連鎖リストがありますが、主に以下の3つの特性を組み合わせることで8種類の構造が考えられます:

  • 単方向または双方向
  • ヘッダありまたはヘッダなし
  • 循環または非循環

最も一般的に使用される構造は以下の2つです:

  • ヘッダなし単方向非循環連鎖リスト:構造がシンプルで、通常は他のデータ構造のサブ構造として使用されます。
  • ヘッダあり双方向循環連鎖リスト:構造が複雑ですが、多くの利点があるため実装が比較的容易となります。

単方向連鎖リストの実装

以下に実装するインターフェースを示します:


// ノードの動的確保
ListNode* createNode(int value);
// 連鎖リストの表示
void printList(ListNode* head);
// 末尾への挿入
void insertAtTail(ListNode** head, int value);
// 先頭への挿入
void insertAtHead(ListNode** head, int value);
// 末尾からの削除
void removeAtTail(ListNode** head);
// 先頭からの削除
void removeAtHead(ListNode** head);
// 値の検索
ListNode* searchValue(ListNode* head, int value);
// 指定位置の後の挿入
void insertAfter(ListNode* pos, int value);
// 指定位置の後の削除
void removeAfter(ListNode* pos);
// 指定位置の前に挿入
void insertBefore(ListNode** head, ListNode* pos, int value);
// 指定位置の削除
void removePosition(ListNode** head, ListNode* pos);
// 連鎖リストの破棄
void destroyList(ListNode** head);

1. ノードの定義


typedef int NodeData;

typedef struct Node {
    NodeData data;
    struct Node* next;
}ListNode;

2. 連鎖リストの表示


void printList(ListNode* head) {
    ListNode* current = head;
    while (current != NULL) {
        printf("%d -> ", current->data);
        current = current->next;
    }
    printf("NULL\n");
}

3. 末尾への挿入


void insertAtTail(ListNode** head, int value) {
    ListNode* newNode = createNode(value);
    if (*head == NULL) {
        *head = newNode;
    } else {
        ListNode* tail = *head;
        while (tail->next != NULL) {
            tail = tail->next;
        }
        tail->next = newNode;
    }
}

4. 先頭への挿入


void insertAtHead(ListNode** head, int value) {
    ListNode* newNode = createNode(value);
    newNode->next = *head;
    *head = newNode;
}

5. 末尾からの削除


void removeAtTail(ListNode** head) {
    if (*head == NULL) return;
    if ((*head)->next == NULL) {
        free(*head);
        *head = NULL;
        return;
    }
    ListNode* current = *head;
    while (current->next->next != NULL) {
        current = current->next;
    }
    free(current->next);
    current->next = NULL;
}

6. 先頭からの削除


void removeAtHead(ListNode** head) {
    if (*head == NULL) return;
    ListNode* temp = *head;
    *head = (*head)->next;
    free(temp);
}

7. 値の検索


ListNode* searchValue(ListNode* head, int value) {
    ListNode* current = head;
    while (current != NULL) {
        if (current->data == value) {
            return current;
        }
        current = current->next;
    }
    return NULL;
}

8. 指定位置の前の挿入


void insertBefore(ListNode** head, ListNode* pos, int value) {
    if (pos == *head) {
        insertAtHead(head, value);
    } else {
        ListNode* current = *head;
        while (current->next != pos) {
            current = current->next;
        }
        ListNode* newNode = createNode(value);
        newNode->next = pos;
        current->next = newNode;
    }
}

9. 指定位置の後の挿入


void insertAfter(ListNode* pos, int value) {
    ListNode* newNode = createNode(value);
    newNode->next = pos->next;
    pos->next = newNode;
}

10. 指定位置の削除


void removePosition(ListNode** head, ListNode* pos) {
    if (pos == *head) {
        removeAtHead(head);
    } else {
        ListNode* current = *head;
        while (current->next != pos) {
            current = current->next;
        }
        current->next = pos->next;
        free(pos);
    }
}

11. 指定位置の後の削除


void removeAfter(ListNode* pos) {
    if (pos->next == NULL) return;
    ListNode* temp = pos->next;
    pos->next = pos->next->next;
    free(temp);
}

12. 連鎖リストの破棄


void destroyList(ListNode** head) {
    ListNode* current = *head;
    while (current != NULL) {
        ListNode* temp = current->next;
        free(current);
        current = temp;
    }
    *head = NULL;
}

双方向循環連鎖リストの実装

1. ノードの定義


typedef int DNodeData;

typedef struct DNode {
    struct DNode* next;
    struct DNode* prev;
    DNodeData data;
}DListNode;

2. 双方向循環連鎖リストの初期化


void initList(DListNode** head) {
    *head = createDNode(-1);
    (*head)->next = *head;
    (*head)->prev = *head;
}

3. 双方向循環連鎖リストの表示


void printDList(DListNode* head) {
    assert(head);
    DListNode* current = head->next;
    printf("<=head=>");
    while (current != head) {
        printf("%d<=>", current->data);
        current = current->next;
    }
    printf("\n");
}

4. 末尾への挿入


void insertAtTail(DListNode* head, DNodeData value) {
    assert(head);
    DListNode* newNode = createDNode(value);
    DListNode* tail = head->prev;
    tail->next = newNode;
    newNode->prev = tail;
    newNode->next = head;
    head->prev = newNode;
}

5. リストが空かどうかのチェック


bool isEmpty(DListNode* head) {
    assert(head);
    return head->next == head;
}

6. 末尾からの削除


void removeAtTail(DListNode* head) {
    assert(head);
    assert(!isEmpty(head));
    DListNode* tail = head->prev;
    DListNode* prevTail = tail->prev;
    prevTail->next = head;
    head->prev = prevTail;
    free(tail);
}

7. 先頭への挿入


void insertAtHead(DListNode* head, DNodeData value) {
    assert(head);
    DListNode* newNode = createDNode(value);
    newNode->next = head->next;
    head->next->prev = newNode;
    head->next = newNode;
    newNode->prev = head;
}

8. 指定位置の前の挿入


void insertBefore(DListNode* pos, DNodeData value) {
    assert(pos);
    DListNode* prevPos = pos->prev;
    DListNode* newNode = createDNode(value);
    prevPos->next = newNode;
    newNode->prev = prevPos;
    newNode->next = pos;
    pos->prev = newNode;
}

タグ: C言語 連鎖リスト データ構造

10月8日 12:21 投稿