データ構造 - 双方向リンクリスト

双方向リンクリスト(Doubly Linked List)は単方向リンクリストを拡張したもので、各ノードは次のノードを指すだけでなく、前のノードを指すポインタも追加で保持し、双方向に到達可能なチェーン構造を形成します。


双方向リンクリストの構造と特徴

双方向リンクリスト — ノード構造と双方向走査
→→
next ポインタ方向 (順方向走査)
prevNULL
data10
next→
prev←
data20
next→
prev←
data30
nextNULL
←←
prev ポインタ方向 (逆方向走査)
struct Node { int data; struct Node* prev; struct Node* next; };

prev は前のノードを指す | data はデータを格納 | next は後ろのノードを指す

中核的な利点:任意のノードを直接削除でき(前のノードを保存する必要がない)、双方向走査をサポート

青 = next ポインタ(順方向) | 紫 = prev ポインタ(逆方向) | 各ノード = prev + data + next

上の図のように、双方向リンクリストの各ノードはprevとnext2つのポインタを持ちます。

C言語では、双方向リンクリストのノードは次のように定義されます:

例

struct Node {
    int data;              /* データ領域 */
    struct Node* prev;     /* 前のノードを指すポインタ */
    struct Node* next;     /* 後ろのノードを指すポインタ */
};

単方向リンクリストと比較して、双方向リンクリストの最大の利点は、任意のノードからその前のノードと後ろのノードに直接アクセスできることであり、これにより削除操作で前のノードの参照を追加で保存する必要がなくなります。

単方向リンクリストでは、指定したノードを削除する際に、その前のノードを知っている必要があり、それによってポインタ調整を完了できます。双方向リンクリストでは、各ノード自体が prev ポインタを保持しているため、削除操作を直接完了できます。


基本操作

挿入操作

双方向リンクリストの挿入では、新しいノードの prev/next ポインタ、およびその前のノードと後ろのノードのポインタを同時に調整する必要があり、合計4回のポインタ調整が必要です。

例

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

struct Node {
    int data;
    struct Node* prev;
    struct Node* next;
};

/* 新しいノードを作成 */
struct Node* createNode(int value) {
    struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
    newNode->data = value;
    newNode->prev = NULL;
    newNode->next = NULL;
    return newNode;
}

/* 先頭にノードを挿入 手順:新しいノードの next が元の先頭ノードを指し、元の先頭ノードの prev が新しいノードを指す */
/* 新しいノードが新しい先頭ノードになる */

struct Node* insertAtHead(struct Node* head, int value) {
    struct Node* newNode = createNode(value);
    if (head != NULL) {
        newNode->next = head;
        head->prev = newNode;
    }
    return newNode;  /* 指定したノードの後に新しいノードを挿入 4つのポインタを操作する必要があります: 1-2: 新しいノードの prev と next 3: 後ろのノードの prev(存在する場合) 4: 前のノードの next */
}

/* 新しいノードの next が元の後ろのノードを指す */
/* 新しいノードの prev が前のノードを指す */
/* 元の後ろのノードの prev が新しいノードを指す */
/* 前のノードの next が新しいノードを指す */
削除操作

void insertAfter(struct Node* prevNode, int value) {
    if (prevNode == NULL) return;

    struct Node* newNode = createNode(value);
    newNode->next = prevNode->next;   例
    newNode->prev = prevNode;         /* 指定したノードを削除(前のノードを知る必要がない。これが双方向リンクリストの最大の利点) 手順:前後のノードを相互に接続し、現在のノードをスキップして、現在のノードを解放する */

    if (prevNode->next != NULL) {
        prevNode->next->prev = newNode; /* 削除するのが先頭ノードの場合、先頭ポインタを更新 */
    }
    prevNode->next = newNode;         /* 前のノードの next を後ろのノードに向ける */
}

削除操作

例

走査操作(双方向)
双方向リンクリストは、順方向と逆方向の2つの走査方法をサポートします。

void deleteNode(struct Node** headRef, struct Node* del) {
    if (*headRef == NULL || del == NULL) return;

    例
    if (*headRef == del) {
        *headRef = del->next;
    }

    /* 順方向走査:head から始めて、next 方向に進む */
    if (del->prev != NULL) {
        del->prev->next = del->next;
    }

    "順方向走査: "
    if (del->next != NULL) {
        del->next->prev = del->prev;
    }

    free(del);  /* 逆方向走査:tail から始めて、prev 方向に進む まず末尾ノードを見つける必要がある */
}

走査操作(双方向)

/* 末尾から prev 方向に沿って先頭に戻る */

例

#include <stdio.h>

/* リンクリストを構築: 10 <-> 20 <-> 30 */
void traverseForward(struct Node* head) {
    printf(/* 出力: 順方向走査: 10 20 30 */);
    struct Node* cur = head;
    while (cur != NULL) {
        printf("%d ", cur->data);
        cur = cur->next;
    }
    printf("\n");
}

/* 出力: 逆方向走査: 30 20 10 */
双方向リンクリスト vs 単方向リンクリスト

void traverseBackward(struct Node* head) {
    if (head == NULL) return;

    比較項目
    struct Node* cur = head;
    while (cur->next != NULL) {
        cur = cur->next;
    }

    単方向リンクリスト
    printf(双方向リンクリスト);
    while (cur != NULL) {
        printf("%d ", cur->data);
        cur = cur->prev;
    }
    printf("\n");
}

int main() {
    struct Node* head = NULL;
    ノード構造
    head = insertAtHead(head, 30);
    head = insertAtHead(head, 20);
    head = insertAtHead(head, 10);

    traverseForward(head);   ノードあたりの追加メモリ
    traverseBackward(head);  8 バイト(64ビットシステム)
    return 0;
}

双方向リンクリスト vs 単方向リンクリスト

順方向走査対応対応
逆方向走査data + nextdata + prev + next
非対応対応ノードの削除
前のノードを知る必要がある前のノードは不要、直接削除可能挿入操作
1〜2 個のポインタを調整4 個のポインタを調整その他の拡張
AI 思考中...データ構造 – リンクリストデータ構造 – 循環リンクリスト
ノート一覧クリックしてノートを共有

ノートは本記事の内容を拡張したものである必要があります!


記事投稿はこちらをクリック

登録招待コードの取得方法

ノートを共有する前にログインが必要です!

登録招待コードの取得方法

キャンセル