现在位置: 首页 > C 语言数据结构与算法 > 正文

数据结构 - 双向链表

双向链表(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

如上图所示,双向链表的每个节点拥有 prevnext 两个指针。

在 C 语言中,双向链表节点的定义如下:

实例

struct Node {
    int data;              /* 数据域 */
    struct Node* prev;     /* 前驱指针:指向前一个节点 */
    struct Node* next;     /* 后继指针:指向后一个节点 */
};

相比单链表,双向链表最大的优势在于可以从任意节点直接访问其前驱和后继,这使得删除操作不再需要额外保存前驱节点的引用。

在单链表中,删除指定节点时必须知道其前驱节点才能完成指针调整。在双向链表中,由于每个节点本身就保存了 prev 指针,删除操作可以直接完成。


基本操作

插入操作

双向链表的插入需要同时调整新节点的 prev/next 指针,以及其前驱和后继节点的指针,共涉及四次指针调整。

实例

#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 */

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

    struct Node* newNode = createNode(value);
    newNode->next = prevNode->next;   /* 新节点的 next 指向原后继 */
    newNode->prev = prevNode;         /* 新节点的 prev 指向前驱 */

    if (prevNode->next != NULL) {
        prevNode->next->prev = newNode; /* 原后继的 prev 指向新节点 */
    }
    prevNode->next = newNode;         /* 前驱的 next 指向新节点 */
}

删除操作

实例

/* 删除指定节点(不需要知道前驱节点,这是双向链表最大的优势)
   步骤:让前后节点互连,跳过当前节点,然后释放当前节点 */

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

    /* 如果删除的是头节点,更新头指针 */
    if (*headRef == del) {
        *headRef = del->next;
    }

    /* 让前驱节点的 next 指向后继节点 */
    if (del->prev != NULL) {
        del->prev->next = del->next;
    }

    /* 让后继节点的 prev 指向前驱节点 */
    if (del->next != NULL) {
        del->next->prev = del->prev;
    }

    free(del);  /* 释放被删除的节点 */
}

遍历操作(双向)

双向链表支持正向和反向两种遍历方式。

实例

#include <stdio.h>

/* 正向遍历:从 head 开始,沿 next 方向 */
void traverseForward(struct Node* head) {
    printf("正向遍历: ");
    struct Node* cur = head;
    while (cur != NULL) {
        printf("%d ", cur->data);
        cur = cur->next;
    }
    printf("\n");
}

/* 反向遍历:从 tail 开始,沿 prev 方向
   需要先找到尾部节点 */

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

    /* 先走到最后一个节点(即尾部) */
    struct Node* cur = head;
    while (cur->next != NULL) {
        cur = cur->next;
    }

    /* 从尾部沿 prev 方向返回头部 */
    printf("反向遍历: ");
    while (cur != NULL) {
        printf("%d ", cur->data);
        cur = cur->prev;
    }
    printf("\n");
}

int main() {
    struct Node* head = NULL;
    /* 构建链表: 10 <-> 20 <-> 30 */
    head = insertAtHead(head, 30);
    head = insertAtHead(head, 20);
    head = insertAtHead(head, 10);

    traverseForward(head);   /* 输出: 正向遍历: 10 20 30 */
    traverseBackward(head);  /* 输出: 反向遍历: 30 20 10 */
    return 0;
}

双向链表 vs 单链表

对比维度单链表双向链表
节点结构data + nextdata + prev + next
每节点额外内存8 字节(64位系统)16 字节(64位系统)
正向遍历支持支持
反向遍历不支持支持
删除节点需要知道前驱节点不需前驱,可直接删除
插入操作调整 1~2 个指针调整 4 个指针