1 引言
链表的插入和删除是链表操作的核心。理解这些操作的关键在于:指针指向的是节点,修改的是节点之间的连接关系。
text
原链表:head → [A] → [B] → [C] → NULL
插入节点 X 到 A 和 B 之间:
head → [A] → [X] → [B] → [C] → NULL
↑
修改 A->next 指向 X
修改 X->next 指向 B
删除节点 B:
head → [A] → [X] → [C] → NULL
↑
修改 X->next 指向 C
释放 B 的内存
本章我们将详细剖析这些指针操作。
2 节点的结构定义
c
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node *next;
} Node;
3 插入操作
3.1 头插法
头插法是在链表头部插入新节点,新节点成为新的头节点。
c
/* 头插法:返回新的头指针 */
Node* insert_head(Node *head, int value)
{
Node *new_node = (Node*)malloc(sizeof(Node));
if (new_node == NULL) {
printf("内存分配失败\n");
return head;
}
new_node->data = value;
new_node->next = head; /* 新节点指向原头节点 */
return new_node; /* 新节点成为新的头节点 */
}
指针变化过程:
text
插入前:head → [A] → [B] → NULL
步骤1:new_node = [X] next = ?
步骤2:new_node->next = head → [X] → [A]
步骤3:head = new_node → head → [X] → [A] → [B] → NULL
时间复杂度:O(1)
3.2 尾插法
尾插法是在链表尾部插入新节点。
c
/* 尾插法:返回头指针(可能不变) */
Node* insert_tail(Node *head, int value)
{
Node *new_node = (Node*)malloc(sizeof(Node));
if (new_node == NULL) {
printf("内存分配失败\n");
return head;
}
new_node->data = value;
new_node->next = NULL;
/* 空链表特殊处理 */
if (head == NULL) {
return new_node;
}
/* 找到最后一个节点 */
Node *current = head;
while (current->next != NULL) {
current = current->next;
}
current->next = new_node;
return head;
}
指针变化过程:
text
插入前:head → [A] → [B] → NULL
步骤1:找到最后一个节点 B
步骤2:B->next = new_node → [B] → [X] → NULL
时间复杂度:O(n)(需要遍历到末尾)
3.3 指定位置插入
在指定位置(从0开始计数)插入新节点。
c
/* 在指定位置插入(位置从0开始) */
Node* insert_at(Node *head, int pos, int value)
{
if (pos < 0) {
printf("位置无效\n");
return head;
}
Node *new_node = (Node*)malloc(sizeof(Node));
if (new_node == NULL) {
printf("内存分配失败\n");
return head;
}
new_node->data = value;
/* 插入头部 */
if (pos == 0) {
new_node->next = head;
return new_node;
}
/* 找到要插入位置的前一个节点 */
Node *prev = head;
for (int i = 0; i < pos - 1 && prev != NULL; i++) {
prev = prev->next;
}
/* 位置超出链表长度 */
if (prev == NULL) {
printf("位置超出链表长度\n");
free(new_node);
return head;
}
/* 插入节点 */
new_node->next = prev->next;
prev->next = new_node;
return head;
}
指针变化过程(插入到位置2,即B之前):
text
原链表:head → [A] → [B] → [C] → NULL
位置: 0 1 2
步骤1:找到位置2的前一个节点 B(位置1)
prev = B
步骤2:new_node->next = prev->next → [X] → [C]
步骤3:prev->next = new_node → [B] → [X] → [C]
结果:head → [A] → [B] → [X] → [C] → NULL
4 删除操作
4.1 删除头节点
c
/* 删除头节点,返回新的头指针 */
Node* delete_head(Node *head)
{
if (head == NULL) {
printf("链表为空\n");
return NULL;
}
Node *temp = head; /* 保存要删除的节点 */
head = head->next; /* 头指针指向下一个节点 */
free(temp); /* 释放原头节点 */
return head;
}
指针变化过程:
text
删除前:head → [A] → [B] → [C] → NULL
步骤1:temp = head(指向A)
步骤2:head = head->next(head指向B)
步骤3:free(temp)(释放A)
结果:head → [B] → [C] → NULL
4.2 删除尾节点
c
/* 删除尾节点 */
Node* delete_tail(Node *head)
{
if (head == NULL) {
printf("链表为空\n");
return NULL;
}
/* 只有一个节点的情况 */
if (head->next == NULL) {
free(head);
return NULL;
}
/* 找到倒数第二个节点 */
Node *current = head;
while (current->next->next != NULL) {
current = current->next;
}
/* 释放尾节点 */
free(current->next);
current->next = NULL;
return head;
}
指针变化过程:
text
删除前:head → [A] → [B] → [C] → NULL
步骤1:找到倒数第二个节点 B(current = B)
步骤2:free(current->next)(释放C)
步骤3:current->next = NULL(B->next = NULL)
结果:head → [A] → [B] → NULL
4.3 删除指定位置的节点
c
/* 删除指定位置的节点(位置从0开始) */
Node* delete_at(Node *head, int pos)
{
if (head == NULL) {
printf("链表为空\n");
return NULL;
}
if (pos < 0) {
printf("位置无效\n");
return head;
}
/* 删除头节点 */
if (pos == 0) {
Node *temp = head;
head = head->next;
free(temp);
return head;
}
/* 找到要删除节点的前一个节点 */
Node *prev = head;
for (int i = 0; i < pos - 1 && prev->next != NULL; i++) {
prev = prev->next;
}
/* 位置无效 */
if (prev->next == NULL) {
printf("位置超出链表长度\n");
return head;
}
/* 删除节点 */
Node *temp = prev->next;
prev->next = temp->next;
free(temp);
return head;
}
指针变化过程(删除位置1的节点,即B):
text
删除前:head → [A] → [B] → [C] → NULL
步骤1:找到位置1的前一个节点 A(prev = A)
步骤2:temp = prev->next(temp指向B)
步骤3:prev->next = temp->next(A->next指向C)
步骤4:free(temp)(释放B)
结果:head → [A] → [C] → NULL
4.4 删除第一个值为value的节点
c
/* 删除第一个值为value的节点 */
Node* delete_value(Node *head, int value)
{
if (head == NULL) {
printf("链表为空\n");
return NULL;
}
/* 头节点就是要删除的节点 */
if (head->data == value) {
Node *temp = head;
head = head->next;
free(temp);
return head;
}
/* 查找要删除的节点 */
Node *prev = head;
Node *current = head->next;
while (current != NULL && current->data != value) {
prev = current;
current = current->next;
}
/* 找到则删除 */
if (current != NULL) {
prev->next = current->next;
free(current);
} else {
printf("未找到值 %d\n", value);
}
return head;
}
5 完整示例:链表管理器
c
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node *next;
} Node;
/* 创建节点 */
Node* create_node(int value)
{
Node *new_node = (Node*)malloc(sizeof(Node));
if (new_node == NULL) return NULL;
new_node->data = value;
new_node->next = NULL;
return new_node;
}
/* 打印链表 */
void print_list(Node *head)
{
Node *current = head;
printf("链表:");
while (current != NULL) {
printf("%d ", current->data);
current = current->next;
}
printf("\n");
}
/* 获取链表长度 */
int list_length(Node *head)
{
int len = 0;
Node *current = head;
while (current != NULL) {
len++;
current = current->next;
}
return len;
}
/* 头插法 */
Node* insert_head(Node *head, int value)
{
Node *new_node = create_node(value);
if (new_node == NULL) return head;
new_node->next = head;
return new_node;
}
/* 尾插法 */
Node* insert_tail(Node *head, int value)
{
Node *new_node = create_node(value);
if (new_node == NULL) return head;
if (head == NULL) return new_node;
Node *current = head;
while (current->next != NULL) {
current = current->next;
}
current->next = new_node;
return head;
}
/* 指定位置插入 */
Node* insert_at(Node *head, int pos, int value)
{
if (pos < 0) {
printf("位置无效\n");
return head;
}
if (pos == 0) return insert_head(head, value);
Node *prev = head;
for (int i = 0; i < pos - 1 && prev != NULL; i++) {
prev = prev->next;
}
if (prev == NULL) {
printf("位置超出链表长度\n");
return head;
}
Node *new_node = create_node(value);
if (new_node == NULL) return head;
new_node->next = prev->next;
prev->next = new_node;
return head;
}
/* 删除头节点 */
Node* delete_head(Node *head)
{
if (head == NULL) {
printf("链表为空\n");
return NULL;
}
Node *temp = head;
head = head->next;
free(temp);
return head;
}
/* 删除尾节点 */
Node* delete_tail(Node *head)
{
if (head == NULL) {
printf("链表为空\n");
return NULL;
}
if (head->next == NULL) {
free(head);
return NULL;
}
Node *current = head;
while (current->next->next != NULL) {
current = current->next;
}
free(current->next);
current->next = NULL;
return head;
}
/* 删除指定位置节点 */
Node* delete_at(Node *head, int pos)
{
if (head == NULL) {
printf("链表为空\n");
return NULL;
}
if (pos < 0) {
printf("位置无效\n");
return head;
}
if (pos == 0) return delete_head(head);
Node *prev = head;
for (int i = 0; i < pos - 1 && prev->next != NULL; i++) {
prev = prev->next;
}
if (prev->next == NULL) {
printf("位置超出链表长度\n");
return head;
}
Node *temp = prev->next;
prev->next = temp->next;
free(temp);
return head;
}
/* 删除值为value的节点 */
Node* delete_value(Node *head, int value)
{
if (head == NULL) return NULL;
if (head->data == value) {
Node *temp = head;
head = head->next;
free(temp);
return head;
}
Node *prev = head;
Node *current = head->next;
while (current != NULL && current->data != value) {
prev = current;
current = current->next;
}
if (current != NULL) {
prev->next = current->next;
free(current);
} else {
printf("未找到值 %d\n", value);
}
return head;
}
/* 释放整个链表 */
void free_list(Node *head)
{
Node *current = head;
Node *next;
while (current != NULL) {
next = current->next;
free(current);
current = next;
}
}
/* 显示菜单 */
void show_menu(void)
{
printf("\n========== 链表操作菜单 ==========\n");
printf("1. 头插法\n");
printf("2. 尾插法\n");
printf("3. 指定位置插入\n");
printf("4. 删除头节点\n");
printf("5. 删除尾节点\n");
printf("6. 删除指定位置节点\n");
printf("7. 删除值为value的节点\n");
printf("8. 打印链表\n");
printf("9. 获取链表长度\n");
printf("0. 退出\n");
printf("==================================\n");
printf("请选择:");
}
int main(void)
{
Node *head = NULL;
int choice, value, pos;
do {
show_menu();
scanf("%d", &choice);
switch (choice) {
case 1:
printf("请输入要插入的值:");
scanf("%d", &value);
head = insert_head(head, value);
printf("插入成功\n");
break;
case 2:
printf("请输入要插入的值:");
scanf("%d", &value);
head = insert_tail(head, value);
printf("插入成功\n");
break;
case 3:
printf("请输入位置和值(位置 值):");
scanf("%d%d", &pos, &value);
head = insert_at(head, pos, value);
break;
case 4:
head = delete_head(head);
break;
case 5:
head = delete_tail(head);
break;
case 6:
printf("请输入要删除的位置:");
scanf("%d", &pos);
head = delete_at(head, pos);
break;
case 7:
printf("请输入要删除的值:");
scanf("%d", &value);
head = delete_value(head, value);
break;
case 8:
print_list(head);
break;
case 9:
printf("链表长度:%d\n", list_length(head));
break;
case 0:
printf("退出程序,释放链表...\n");
free_list(head);
break;
default:
printf("无效选择\n");
}
} while (choice != 0);
return 0;
}
6 常见错误与注意事项
6.1 插入时忘记处理空链表
c
/* 错误:没有检查 head 是否为 NULL */
Node* insert_tail_bad(Node *head, int value)
{
Node *new_node = create_node(value);
Node *current = head;
while (current->next != NULL) { /* 如果 head 为 NULL,这里崩溃 */
current = current->next;
}
current->next = new_node;
return head;
}
6.2 删除时忘记保存要释放的节点
c
/* 错误:直接移动指针,无法释放原节点 */
Node* delete_head_bad(Node *head)
{
head = head->next; /* 原头节点丢失,无法释放,造成内存泄漏 */
return head;
}
6.3 删除尾节点时没有处理只有一个节点的情况
c
/* 错误:假设链表至少有两个节点 */
Node* delete_tail_bad(Node *head)
{
Node *current = head;
while (current->next->next != NULL) { /* 只有一个节点时崩溃 */
current = current->next;
}
free(current->next);
current->next = NULL;
return head;
}
6.4 位置插入/删除时边界检查不足
c
/* 错误:没有检查位置是否有效 */
Node* insert_at_bad(Node *head, int pos, int value)
{
Node *prev = head;
for (int i = 0; i < pos - 1; i++) { /* 可能访问 NULL */
prev = prev->next;
}
/* ... */
}
6.5 内存泄漏
c
void func(void)
{
Node *head = create_list();
/* 使用链表... */
/* 忘记 free_list(head) */
} /* 链表内存泄漏 */
6.6 指针悬挂
c
Node *p = head;
free_list(head);
p->data = 10; /* 错误!p 指向已释放的内存 */
7 本章小结
本章系统介绍了链表的插入与删除操作:
1. 插入操作
| 操作 | 步骤 | 时间复杂度 |
|---|---|---|
| 头插法 | 新节点→原头节点,头指针指向新节点 | O(1) |
| 尾插法 | 找到尾节点,尾节点→新节点 | O(n) |
| 指定位置 | 找到前一个节点,修改指针 | O(n) |
2. 删除操作
| 操作 | 步骤 | 时间复杂度 |
|---|---|---|
| 删除头节点 | 头指针指向下一个节点,释放原头节点 | O(1) |
| 删除尾节点 | 找到倒数第二个节点,释放尾节点 | O(n) |
| 删除指定位置 | 找到前一个节点,修改指针,释放目标节点 | O(n) |
3. 指针修改的核心逻辑
-
插入 :
new->next = prev->next; prev->next = new; -
删除 :
prev->next = target->next; free(target);
4. 边界情况处理
-
空链表(head == NULL)
-
只有一个节点
-
插入/删除位置为0(头节点)
-
插入/删除位置为末尾
-
位置超出链表长度
5. 注意事项
-
总是检查
malloc返回值 -
操作前保存要释放的节点地址
-
修改指针前考虑所有边界情况
-
使用后必须释放内存