一、双向链表结构与基础函数
cpp
#include <stdio.h>
#include <stdlib.h>
// 定义链表存储的数据类型为int
typedef int ElemType;
// 定义双向链表节点结构
typedef struct node {
ElemType data; // 数据域:存储节点数据
struct node *next; // 后继指针:指向后一个节点
struct node *prev; // 前驱指针:指向前一个节点
} Node;
/**
* 初始化双向链表(带头节点)
* 返回值:指向头节点的指针
*/
Node* initList() {
// 动态分配头节点内存
Node *head = (Node*)malloc(sizeof(Node));
if (head == NULL) { // 内存分配失败判断
printf("内存分配失败!\n");
exit(1);
}
head->data = 0; // 头节点数据域无实际意义,仅作占位
head->next = NULL; // 后继指针初始化为空(链表初始为空)
head->prev = NULL; // 前驱指针初始化为空
return head;
}
/**
* 获取双向链表的尾节点
* 参数 L:链表头节点指针
* 返回值:尾节点指针
*/
Node* get_tail(Node *L) {
Node *p = L;
// 遍历链表,直到找到next为NULL的节点(即尾节点)
while (p->next != NULL) {
p = p->next;
}
return p;
}
/**
* 双向链表尾插法(在链表末尾插入新节点)
* 参数 tail:当前尾节点指针
* 参数 e:待插入的数据
* 返回值:新的尾节点指针
*/
Node* insertTail(Node *tail, ElemType e) {
// 1. 为新节点分配内存
Node *p = (Node*)malloc(sizeof(Node));
if (p == NULL) {
printf("内存分配失败!\n");
exit(1);
}
// 2. 给新节点赋值
p->data = e; // 新节点数据域 = 待插入数据
p->prev = tail; // 新节点前驱指针 = 当前尾节点
tail->next = p; // 当前尾节点的后继指针 = 新节点
p->next = NULL; // 新节点作为新尾节点,后继指针置空
// 3. 返回新的尾节点指针
return p;
}
/**
* 遍历并打印双向链表(从第一个有效节点开始)
* 参数 L:链表头节点指针
*/
void listNode(Node* L) {
Node *p = L->next; // 从第一个有效节点开始遍历(跳过头节点)
while (p != NULL) { // 遍历到链表末尾(next为NULL时停止)
printf("%d ", p->data); // 打印当前节点数据
p = p->next; // 指针后移
}
printf("\n"); // 遍历结束后换行
}
二、指定位置插入函数
cpp
/**
* 双向链表指定位置插入(在第pos个位置后插入新节点,pos从1开始)
* 参数 L:链表头节点指针
* 参数 pos:插入位置(1为第一个有效节点的位置)
* 参数 e:待插入的数据
* 返回值:1表示插入成功,0表示插入失败
*/
int insertNode(Node *L, int pos, ElemType e) {
Node *p = L;
int i = 0;
// 1. 遍历找到第pos-1个节点(即插入位置的前驱节点)
while (i < pos - 1) {
p = p->next;
i++;
// 如果遍历到链表末尾仍未找到目标位置,说明pos非法
if (p == NULL) {
return 0;
}
}
// 2. 为新节点分配内存
Node *q = (Node*)malloc(sizeof(Node));
if (q == NULL) {
printf("内存分配失败!\n");
exit(1);
}
// 3. 给新节点赋值
q->data = e; // 新节点数据域 = 待插入数据
q->prev = p; // 新节点前驱 = 第pos-1个节点
q->next = p->next; // 新节点后继 = 原第pos个节点
// 4. 如果原第pos个节点存在,修改其前驱指针
if (p->next != NULL) {
p->next->prev = q;
}
// 5. 修改前驱节点的后继指针,完成插入
p->next = q;
return 1; // 插入成功
}
三、删除节点核心代码(重点)

cpp
/**
* 双向链表删除指定位置节点(删除第pos个节点,pos从1开始)
* 参数 L:链表头节点指针
* 参数 pos:删除位置(1为第一个有效节点的位置)
* 返回值:1表示删除成功,0表示删除失败
*/
int deleteNode(Node *L, int pos) {
Node *p = L;
int i = 0;
// 1. 遍历找到第pos-1个节点(即要删除节点的前驱节点)
while (i < pos - 1) {
p = p->next;
i++;
// 如果遍历到链表末尾仍未找到目标位置,说明pos非法
if (p == NULL) {
return 0;
}
}
// 2. 判断要删除的节点是否存在
if (p->next == NULL) {
printf("要删除的位置错误\n");
return 0;
}
// 3. 用指针q记录要删除的节点
Node *q = p->next;
// 4. 重新建立双向链接,跳过要删除的节点
p->next = q->next; // 前驱节点的后继 = 要删除节点的后继
// 如果要删除的节点不是尾节点,修改后继节点的前驱
if (q->next != NULL) {
q->next->prev = p;
}
// 5. 释放要删除节点的内存,避免内存泄漏
free(q);
return 1; // 删除成功
}
删除节点核心思想:

- 定位前驱节点:先遍历到要删除节点的前一个节点(
pos-1位置),保证能安全修改链表结构。 - 记录待删节点:用指针
q暂存要删除的节点,避免后续指针修改后丢失引用。 - 重建双向链接:
- 让前驱节点
p的next直接指向待删节点的后继节点。 - 如果后继节点存在,让它的
prev指向前驱节点p,保证双向链表结构完整。
- 让前驱节点
- 释放内存:调用
free(q)释放待删节点的内存,避免内存泄漏。 - 边界处理:必须判断
p->next是否为NULL(即删除位置超出链表长度),避免对空指针解引用。
四、释放链表函数
cpp
/**
* 释放双向链表所有节点内存(包括头节点)
* 参数 L:链表头节点指针
*/
void freeList(Node *L) {
Node *p = L->next; // 从第一个有效节点开始
Node *q;
// 遍历释放所有有效节点
while (p != NULL) {
q = p->next; // 暂存下一个节点
free(p); // 释放当前节点
p = q; // 指针后移
}
free(L); // 最后释放头节点
}
五、完整可运行代码(含主函数测试)
cpp
#include <stdio.h>
#include <stdlib.h>
typedef int ElemType;
typedef struct node {
ElemType data;
struct node *next;
struct node *prev;
} Node;
// 初始化双向链表
Node* initList() {
Node *head = (Node*)malloc(sizeof(Node));
if (head == NULL) {
printf("内存分配失败!\n");
exit(1);
}
head->data = 0;
head->next = NULL;
head->prev = NULL;
return head;
}
// 获取双向链表的尾节点
Node* get_tail(Node *L) {
Node *p = L;
while (p->next != NULL) {
p = p->next;
}
return p;
}
// 双向链表尾插法
Node* insertTail(Node *tail, ElemType e) {
Node *p = (Node*)malloc(sizeof(Node));
if (p == NULL) {
printf("内存分配失败!\n");
exit(1);
}
p->data = e;
p->prev = tail;
tail->next = p;
p->next = NULL;
return p;
}
// 遍历双向链表
void listNode(Node* L) {
Node *p = L->next;
while (p != NULL) {
printf("%d ", p->data);
p = p->next;
}
printf("\n");
}
// 双向链表指定位置插入
int insertNode(Node *L, int pos, ElemType e) {
Node *p = L;
int i = 0;
while (i < pos - 1) {
p = p->next;
i++;
if (p == NULL) {
return 0;
}
}
Node *q = (Node*)malloc(sizeof(Node));
if (q == NULL) {
printf("内存分配失败!\n");
exit(1);
}
q->data = e;
q->prev = p;
q->next = p->next;
if (p->next != NULL) {
p->next->prev = q;
}
p->next = q;
return 1;
}
// 双向链表删除指定位置节点
int deleteNode(Node *L, int pos) {
Node *p = L;
int i = 0;
while (i < pos - 1) {
p = p->next;
i++;
if (p == NULL) {
return 0;
}
}
if (p->next == NULL) {
printf("要删除的位置错误\n");
return 0;
}
Node *q = p->next;
p->next = q->next;
if (q->next != NULL) {
q->next->prev = p;
}
free(q);
return 1;
}
// 释放双向链表
void freeList(Node *L) {
Node *p = L->next;
Node *q;
while (p != NULL) {
q = p->next;
free(p);
p = q;
}
free(L);
}
// 主函数:测试双向链表删除节点
int main() {
// 1. 初始化双向链表
Node *list = initList();
// 2. 尾插法插入初始数据
Node *tail = get_tail(list);
tail = insertTail(tail, 10);
tail = insertTail(tail, 20);
tail = insertTail(tail, 30);
printf("初始链表:");
listNode(list); // 输出:10 20 30
// 3. 在第2个位置插入15
insertNode(list, 2, 15);
printf("插入后链表:");
listNode(list); // 输出:10 15 20 30
// 4. 删除第2个位置的节点
deleteNode(list, 2);
printf("删除后链表:");
listNode(list); // 输出:10 20 30
// 5. 释放链表内存
freeList(list);
return 0;
}
六、运行结果说明
执行后输出:
- 初始尾插得到
10 → 20 → 30。 - 在第 2 个位置插入
15后,链表变为10 → 15 → 20 → 30。 - 删除第 2 个位置的节点后,链表恢复为
10 → 20 → 30,符合预期。
七、尾插法核心思想回顾
- 定位尾节点:通过
get_tail遍历找到链表最后一个节点。 - 新节点连接:新节点
p的prev指向原尾节点,原尾节点的next指向新节点,新节点的next置为NULL。 - 更新尾节点:函数返回新节点指针,方便后续连续尾插,避免重复遍历找尾。
- 顺序保证:先建立新节点与原尾节点的双向链接,再将新节点的
next置空,保证链表结构完整。