跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表
C算法

单链表核心操作全实现:查找、插入与删除的深度解析

单链表作为线性表的基础结构,其核心在于指针的灵活操控。内容涵盖查找、指定位置前后插入与删除的全流程实现,重点剖析头结点特殊处理、尾结点空指针风险等边界情况。通过 C 语言原生代码展示内存分配与释放逻辑,对比顺序表存储特性,帮助读者建立扎实的底层数据思维。

协议工匠发布于 2026/3/23更新于 2026/5/53 浏览
单链表核心操作全实现:查找、插入与删除的深度解析

单链表核心操作全实现

单链表是数据结构中最基础也最核心的线性表之一。熟练掌握其查找、指定位置插入与删除等操作,是深入学习算法与数据结构的关键一步。本文将从零实现单链表的常用接口,详细拆解每一步思路与代码细节,帮助大家真正理解指针操作与链表结构。

一、查找操作

思路: 遍历链表,pcur 指向当前节点。循环判断 pcur 是否为空,若不为空则检查数据域是否匹配目标值。匹配成功返回当前节点指针;否则将 pcur 移至下一个节点继续判断。若遍历结束仍未找到,返回 NULL。

代码实现:

SLTNode* SLTFind(SLTNode* phead, SLTDataType x) {
    SLTNode* pcur = phead;
    while (pcur) {
        if (pcur->data == x)
            return pcur;
        pcur = pcur->next;
    }
    return NULL;
}

测试示例:

void test1() {
    SLTNode* head = NULL;
    SLTPushFront(&head, 1);
    SLTPushFront(&head, 2);
    SLTPushFront(&head, 3);
    
    SLTNode* find = SLTFind(head, 1);
    if (find)
        printf("找到了!\n");
    else
        printf("未找到\n");
}

时间复杂度: O(n)

二、指定位置前后插入元素

在指定位置之前插入

思路: 要在 pos 之前插入新节点,必须先找到 pos 的前驱节点 prev。让 prev->next 指向新节点,新节点的 next 指向 pos。

这里有个特殊情况:如果 pos 本身就是头结点,那么逻辑上等同于头插操作。此时可以直接复用头插函数,避免重复编写逻辑。

代码实现:

// 在指定位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) {
    assert(pphead && pos);
    
     (pos == *pphead)
        SLTPushFront(pphead, x);
     {
        SLTNode* newnode = SLTBuyNode(x);
        SLTNode* prev = *pphead;
        
         (prev->next != pos)
            prev = prev->next;
        newnode->next = pos;
        prev->next = newnode;
    }
}
// 只有一个节点或 pos 为头结点时,直接头插
if
else
// 寻找前驱节点
while

时间复杂度: O(n)

在指定位置之后插入

思路: 这个操作相对简单。已知 pos,只需创建新节点,让新节点的 next 指向 pos->next,然后修改 pos->next 指向新节点即可。

注意:当 pos 为尾结点时,pos->next 为 NULL,上述逻辑依然成立,无需特殊处理。

代码实现:

// 在指定位置之后插入
void SLTInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDataType x) {
    assert(pphead && pos);
    SLTNode* newnode = SLTBuyNode(x);
    newnode->next = pos->next;
    pos->next = newnode;
}

时间复杂度: O(1)

三、指定位置前后删除

在指定位置删除

思路: 同样需要找到待删节点 pos 的前驱节点 prev。执行 prev->next = pos->next,然后释放 pos。

边界情况: 如果 pos 是头结点,无法通过遍历找到前驱(因为没有前驱)。此时应复用头删逻辑。

代码实现:

// 在指定位置删除
void SLTErase(SLTNode** pphead, SLTNode* pos) {
    assert(pphead && pos);
    // 头结点删除,复用头删逻辑
    if (pos == *pphead)
        SLTPopFront(pphead);
    else {
        SLTNode* prev = *pphead;
        // 寻找前驱
        while (prev->next != pos)
            prev = prev->next;
        prev->next = pos->next;
        free(pos);
        pos = NULL; // 防止野指针
    }
}

时间复杂度: O(n)

在指定位置之后删除

思路: 直接操作 pos->next。设待删节点为 del,执行 pos->next = del->next,释放 del。

风险提示: 必须确保 pos->next 不为空,否则解引用空指针会导致崩溃。因此断言中需增加 pos->next 的检查。

代码实现:

// 在指定位置之后删除
void SLTEraseAfter(SLTNode** pphead, SLTNode* pos) {
    assert(pphead && pos && pos->next); // 关键:确保有后继节点
    SLTNode* del = pos->next;
    pos->next = del->next;
    free(del);
    del = NULL;
}

时间复杂度: O(1)

四、完整代码展现

为了保持工程规范,我们将接口声明放在头文件,实现在源文件中,并编写测试用例。

SList.h

#ifndef __SLIST_H__
#define __SLIST_H__

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef int SLTDataType;

typedef struct SLTNode {
    SLTDataType data;
    struct SLTNode* next;
} SLTNode;

void SLTPrint(SLTNode* phead);
void SLTDestroy(SLTNode** pphead);
void SLTPushBack(SLTNode** pphead, SLTDataType x);
void SLTPushFront(SLTNode** pphead, SLTDataType x);
void SLTPopBack(SLTNode** pphead);
void SLTPopFront(SLTNode** pphead);
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
void SLTInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDataType x);
void SLTErase(SLTNode** pphead, SLTNode* pos);
void SLTEraseAfter(SLTNode** pphead, SLTNode* pos);

#endif

SList.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"

// 打印链表
void SLTPrint(SLTNode* phead) {
    SLTNode* pcur = phead;
    while (pcur) {
        printf("%d->", pcur->data);
        pcur = pcur->next;
    }
    printf("NULL\n");
}

// 节点申请
SLTNode* SLTBuyNode(SLTDataType x) {
    SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
    if (newnode == NULL) {
        printf("开辟失败!\n");
        exit(-1);
    }
    newnode->data = x;
    newnode->next = NULL;
    return newnode;
}

// 销毁链表
void SLTDestroy(SLTNode** pphead) {
    SLTNode* pcur = *pphead;
    while (pcur) {
        SLTNode* next = pcur->next;
        free(pcur);
        pcur = next;
    }
    *pphead = NULL;
}

// 尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x) {
    assert(pphead);
    SLTNode* newnode = SLTBuyNode(x);
    if (*pphead == NULL)
        *pphead = newnode;
    else {
        SLTNode* pcur = *pphead;
        while (pcur->next != NULL)
            pcur = pcur->next;
        pcur->next = newnode;
    }
}

// 头插
void SLTPushFront(SLTNode** pphead, SLTDataType x) {
    assert(pphead);
    SLTNode* newnode = SLTBuyNode(x);
    newnode->next = *pphead;
    *pphead = newnode;
}

// 尾删
void SLTPopBack(SLTNode** pphead) {
    assert(pphead && *pphead);
    if ((*pphead)->next == NULL) {
        free(*pphead);
        *pphead = NULL;
    } else {
        SLTNode* prev = NULL;
        SLTNode* pcur = *pphead;
        while (pcur->next != NULL) {
            prev = pcur;
            pcur = pcur->next;
        }
        free(pcur);
        prev->next = NULL;
    }
}

// 头删
void SLTPopFront(SLTNode** pphead) {
    assert(pphead && *pphead);
    SLTNode* next = (*pphead)->next;
    free(*pphead);
    *pphead = next;
}

// 查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x) {
    SLTNode* pcur = phead;
    while (pcur) {
        if (pcur->data == x)
            return pcur;
        pcur = pcur->next;
    }
    return NULL;
}

// 在指定位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) {
    assert(pphead && pos);
    if (pos == *pphead)
        SLTPushFront(pphead, x);
    else {
        SLTNode* newnode = SLTBuyNode(x);
        SLTNode* prev = *pphead;
        while (prev->next != pos)
            prev = prev->next;
        newnode->next = pos;
        prev->next = newnode;
    }
}

// 在指定位置之后插入
void SLTInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDataType x) {
    assert(pphead && pos);
    SLTNode* newnode = SLTBuyNode(x);
    newnode->next = pos->next;
    pos->next = newnode;
}

// 在指定位置删除
void SLTErase(SLTNode** pphead, SLTNode* pos) {
    assert(pphead && pos);
    if (pos == *pphead)
        SLTPopFront(pphead);
    else {
        SLTNode* prev = *pphead;
        while (prev->next != pos)
            prev = prev->next;
        prev->next = pos->next;
        free(pos);
        pos = NULL;
    }
}

// 在指定位置之后删除
void SLTEraseAfter(SLTNode** pphead, SLTNode* pos) {
    assert(pphead && pos && pos->next);
    SLTNode* del = pos->next;
    pos->next = del->next;
    free(del);
    del = NULL;
}

test.c

#include "SList.h"

int main() {
    SLTNode* head = NULL;
    
    // 初始化数据
    SLTPushBack(&head, 1);
    SLTPushBack(&head, 2);
    SLTPushBack(&head, 3);
    
    printf("初始链表:\n");
    SLTPrint(head);
    
    // 测试查找
    SLTNode* find = SLTFind(head, 2);
    if (find)
        printf("查找成功:\n");
    
    // 测试插入
    SLTInsert(&head, find, 7);
    printf("插入后:\n");
    SLTPrint(head);
    
    // 测试删除
    SLTErase(&head, find);
    printf("删除后:\n");
    SLTPrint(head);
    
    // 清理内存
    SLTDestroy(&head);
    return 0;
}

五、顺序表和链表的区别

特性顺序表链表
存储方式连续内存空间任意内存空间,通过指针连接
随机访问支持 O(1)不支持,需遍历 O(n)
插入/删除涉及大量移动,O(n)仅需修改指针,O(1)
空间利用率需预分配,可能浪费或不足动态分配,按需使用
缓存局部性好,利于 CPU 缓存命中差,指针跳转导致缓存失效

注:缓存利用率参考存储体系结构及局部性原理。

总结

单链表的学习没有捷径,每一个指针、每一次遍历、每一段接口实现,都是夯实功底的必经之路。保持耐心,多写多练多思考,把基础打牢,复杂的算法与项目自然水到渠成。

目录

  1. 单链表核心操作全实现
  2. 一、查找操作
  3. 二、指定位置前后插入元素
  4. 在指定位置之前插入
  5. 在指定位置之后插入
  6. 三、指定位置前后删除
  7. 在指定位置删除
  8. 在指定位置之后删除
  9. 四、完整代码展现
  10. SList.h
  11. SList.c
  12. test.c
  13. 五、顺序表和链表的区别
  14. 总结
  • 💰 8折买阿里云服务器限时8折了解详情
  • GPT-5.5 超高智商模型1元抵1刀ChatGPT中转购买
  • 代充Chatgpt Plus/pro 帐号了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 基于高阶 CBF 的端到端无人机高速避障:7.5m/s 丛林穿越与 RL 安全突破
  • 文心大模型 4.5 轻量化部署实践与能力验证
  • 跳表核心原理与 C++ 实现深度解析
  • 面向新手的鸿蒙跨平台开发技术选型指南
  • C++ 二叉搜索树原理与实战:插入查找删除及 key/value 场景
  • AI 绘画电商产品提示词撰写指南
  • 二分查找实战:山峰数组峰顶索引与寻找峰值
  • 基于 DeepSeek 的企业级知识管理系统设计与实现
  • 二分查找实战:山峰数组峰顶索引与寻找峰值
  • Qwen3-TTS-VoiceDesign 实战:AR 导览眼镜空间音频定位语音生成
  • 基于 DeepSeek 与通义万相的 AI 视频制作实战指南
  • 在线考试系统的设计与实现
  • Linux Ext2 文件系统深度解析
  • MySQL 分库分表实战:垂直分库与水平分表策略及分片键选择
  • 利用 DeepSeek 提示词与工具降低论文 AIGC 检测率实战
  • 基于 VeRL 框架的 GSPO 算法在 Atlas 800T A2 服务器上实践
  • Llama-2-7B 昇腾 NPU 性能测评与部署实践
  • FLOAT:基于流匹配的音频驱动说话者头像生成模型
  • AVL 树核心原理与 C++ 模拟实现详解
  • AI工具链:Gradio演示界面

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online