【C语言程序设计】第38篇:链表数据结构(二):链表的插入与删除操作

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 返回值

  • 操作前保存要释放的节点地址

  • 修改指针前考虑所有边界情况

  • 使用后必须释放内存

相关推荐
颜酱2 小时前
吃透回溯算法:从框架到实战
javascript·后端·算法
oem1102 小时前
C++中的适配器模式
开发语言·c++·算法
lly2024062 小时前
jQuery 隐藏/显示
开发语言
青木川崎2 小时前
设计模式之面试题
java·开发语言·设计模式
空空潍2 小时前
Java核心基础语法:从原理到实战,夯实Java开发基石
java·开发语言
jing-ya2 小时前
day 57 图论part9
java·开发语言·数据结构·算法·图论
huohuopro2 小时前
详解ThreadLocal的使用
java·开发语言·jvm
2401_894241922 小时前
C++与Rust交互编程
开发语言·c++·算法
cjforever142 小时前
数据结构整理-二叉树
数据结构