数据结构 - 双向链表
双向链表(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 和 next 两个指针。
在 C 语言中,双向链表节点的定义如下:
实例
struct Node {
int data; /* 数据域 */
struct Node* prev; /* 前驱指针:指向前一个节点 */
struct Node* next; /* 后继指针:指向后一个节点 */
};
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 指向新节点 */
}
#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); /* 释放被删除的节点 */
}
步骤:让前后节点互连,跳过当前节点,然后释放当前节点 */
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;
}
/* 正向遍历:从 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 + next | data + prev + next |
| 每节点额外内存 | 8 字节(64位系统) | 16 字节(64位系统) |
| 正向遍历 | 支持 | 支持 |
| 反向遍历 | 不支持 | 支持 |
| 删除节点 | 需要知道前驱节点 | 不需前驱,可直接删除 |
| 插入操作 | 调整 1~2 个指针 | 调整 4 个指针 |
