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

数据结构初阶:单链表

综述由AI生成讲解单链表概念与结构,利用火车厢类比说明非连续存储原理。详细阐述了结点定义、内存分配、打印、增删改查(尾插、头插、尾删、头删、查找、指定位置插入删除)及销毁的 C 语言实现。包含完整头文件、源文件及测试代码,重点展示指针操作与动态内存管理技巧。

禅心发布于 2026/3/21更新于 2026/4/2712 浏览
数据结构初阶:单链表

单链表

1. 链表的概念及结构

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

链表的结构跟火车厢相似,淡季时车次的车厢会相应减少,旺季时车次的车厢会额外增加几节。只需要将火车里的某节车厢去掉/加上,不会影响其他车厢,每节车厢都是独立存在的。车厢是独立存在的,且每节车厢都有车门。想象一下这样的场景,假设每节车厢的车门都是锁上的状态,需要不同的钥匙才能解锁,每次只能携带一把钥匙的情况下如何从车头走到车尾? 最简单的做法:每节车厢里都放一把下一节车厢的钥匙。 在链表里,每节'车厢'是什么样的呢?

与顺序表不同的是,链表里的每节'车厢'都是独立申请下来的空间,我们称之为'结点/节点'。 节点的组成主要有两个部分:当前节点要保存的数据和保存下一个节点的地址(指针变量)。 图中指针变量 plist 保存的是第一个节点的地址,我们称 plist 此时'指向'第一个节点,如果我们希望 plist'指向'第二个节点时,只需要修改 plist 保存的内容为 0x0012FFA0。 为什么还需要指针变量来保存下一个节点的位置? 链表中每个节点都是独立申请的(即需要插入数据时才去申请一块节点的空间),我们需要通过指针变量来保存下一个节点位置才能从当前节点找到下一个节点。 结合前面学到的结构体知识,我们可以给出每个节点对应的结构体代码: 假设当前保存的节点为整型:

struct SListNode {
    int data; // 节点数据
    struct SListNode* next; // 指针变量用于保存下一个节点的地址
};

当我们想要保存一个整型数据时,实际是向操作系统申请了一块内存,这个内存不仅要保存整型数据,也需要保存下一个节点的地址(当下一个节点为空时保存的地址为空)。 当我们想要从第一个节点走到最后一个节点时,只需要在前一个节点拿取下一个节点的地址(下一个节点的钥匙)就可以了。 给定的链表结构中,如何实现节点从头到尾的打印?

2. 单链表的实现

1. 定义结点
typedef int SLTDataType;
typedef struct SListNode { // s single
    SLTDataType data;
    struct SListNode* next;
} SLTNode;
2. 打印数据
void SLTPrint(SLTNode* phead) {
    SLTNode* pcur = phead;
    while (pcur) {
        printf("%d->", pcur->data);
        pcur = pcur->next;
    }
    printf("NULL\n");
}
3. 申请新的节点
SLTNode* SLTBuyNode(SLTDataType x) { SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode)); if (newnode == NULL) { perror("malloc fail!"); exit(1); } newnode->data = x; newnode->next = NULL; return newnode; }
4. 尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x) {
    assert(pphead);
    SLTNode* newnode = SLTBuyNode(x);
    if (*pphead == NULL) {
        *pphead = newnode;
    } else {
        SLTNode* ptail = *pphead;
        while (ptail->next) {
            ptail = ptail->next;
        }
        ptail->next = newnode;
    }
}
5. 头插
void SLTPushFront(SLTNode** pphead, SLTDataType x) {
    assert(pphead);
    SLTNode* newnode = SLTBuyNode(x);
    newnode->next = *pphead;
    *pphead = newnode;
}
6. 尾删
void SLTPopBack(SLTNode** pphead) {
    assert(pphead && *pphead);
    if ((*pphead)->next == NULL) {
        free(*pphead);
        *pphead = NULL;
    } else {
        SLTNode* prev = *pphead;
        SLTNode* ptail = *pphead;
        while (ptail->next) {
            prev = ptail;
            ptail = ptail->next;
        }
        free(ptail);
        ptail = NULL;
        prev->next = NULL;
    }
}
7. 头删
void SLTPopFront(SLTNode** pphead) {
    assert(pphead && *pphead);
    SLTNode* next = (*pphead)->next;
    free(*pphead);
    *pphead = next;
}
8. 查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x) {
    SLTNode* pcur = phead;
    while (pcur) {
        if (pcur->data == x) {
            return pcur;
        }
        pcur = pcur->next;
    }
    return NULL;
}
9. 指定位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) {
    assert(pphead && *pphead);
    assert(pos);
    SLTNode* newnode = SLTBuyNode(x);
    if (pos == *pphead) {
        SLTPushFront(pphead, x);
    } else {
        SLTNode* prev = *pphead;
        while (prev->next != pos) {
            prev = prev->next;
        }
        prev->next = newnode;
        newnode->next = pos;
    }
}
10. 指定位置后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x) {
    assert(pos);
    SLTNode* newnode = SLTBuyNode(x);
    newnode->next = pos->next;
    pos->next = newnode;
}
11. 指定位置前删除
void SLTErase(SLTNode** pphead, SLTNode* pos) {
    assert(pphead && *pphead);
    SLTNode* prev = *pphead;
    if (pos == *pphead) {
        SLTPopFront(pphead);
    } else {
        while (prev->next != pos) {
            prev = prev->next;
        }
        prev->next = pos->next;
        free(pos);
        pos = NULL;
    }
}
12. 指定位置后删除
void SLTEraseAfter(SLTNode* pos) {
    assert(pos && pos->next);
    SLTNode* del = pos->next;
    pos->next = pos->next->next;
    free(del);
}
13. 链表的销毁
void SLTDestroy(SLTNode** pphead) {
    assert(pphead);
    SLTNode* pcur = *pphead;
    while (pcur) {
        SLTNode* next = pcur->next;
        free(pcur);
        pcur = next;
    }
    *pphead = NULL;
}

3. 程序源码

共分三个文件

SList.h 函数的声明

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

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

void SLTPrint(SLTNode* phead);
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* pos, SLTDataType x);
void SLTErase(SLTNode** pphead, SLTNode* pos);
void SLTEraseAfter(SLTNode* pos);
void SLTDestroy(SLTNode** pphead);

SList.c 函数的实现

#define _CRT_SECURE_NO_WARNINGS
#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) {
        perror("malloc fail!");
        exit(1);
    }
    newnode->data = x;
    newnode->next = NULL;
    return newnode;
}

void SLTPushBack(SLTNode** pphead, SLTDataType x) {
    assert(pphead);
    SLTNode* newnode = SLTBuyNode(x);
    if (*pphead == NULL) {
        *pphead = newnode;
    } else {
        SLTNode* ptail = *pphead;
        while (ptail->next) {
            ptail = ptail->next;
        }
        ptail->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 = *pphead;
        SLTNode* ptail = *pphead;
        while (ptail->next) {
            prev = ptail;
            ptail = ptail->next;
        }
        free(ptail);
        ptail = NULL;
        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 && *pphead);
    assert(pos);
    SLTNode* newnode = SLTBuyNode(x);
    if (pos == *pphead) {
        SLTPushFront(pphead, x);
    } else {
        SLTNode* prev = *pphead;
        while (prev->next != pos) {
            prev = prev->next;
        }
        prev->next = newnode;
        newnode->next = pos;
    }
}

void SLTInsertAfter(SLTNode* pos, SLTDataType x) {
    assert(pos);
    SLTNode* newnode = SLTBuyNode(x);
    newnode->next = pos->next;
    pos->next = newnode;
}

void SLTErase(SLTNode** pphead, SLTNode* pos) {
    assert(pphead && *pphead);
    SLTNode* prev = *pphead;
    if (pos == *pphead) {
        SLTPopFront(pphead);
    } else {
        while (prev->next != pos) {
            prev = prev->next;
        }
        prev->next = pos->next;
        free(pos);
        pos = NULL;
    }
}

void SLTEraseAfter(SLTNode* pos) {
    assert(pos && pos->next);
    SLTNode* del = pos->next;
    pos->next = pos->next->next;
    free(del);
}

void SLTDestroy(SLTNode** pphead) {
    assert(pphead);
    SLTNode* pcur = *pphead;
    while (pcur) {
        SLTNode* next = pcur->next;
        free(pcur);
        pcur = next;
    }
    *pphead = NULL;
}

test.c 用来测试

#define _CRT_SECURE_NO_WARNINGS
#include "SList.h"

void SListTest01() {
    SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));
    node1->data = 1;
    SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));
    node2->data = 2;
    SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));
    node3->data = 3;
    SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));
    node4->data = 4;

    node1->next = node2;
    node2->next = node3;
    node3->next = node4;
    node4->next = NULL;

    SLTNode* plist = node1;
    SLTPrint(plist);
}

void Test02() {
    SLTNode* plist = NULL;
    SLTPushBack(&plist, 1);
    SLTPushBack(&plist, 2);
    SLTPushBack(&plist, 3);
    SLTPushBack(&plist, 4);
    SLTPrint(plist);

    SLTPopBack(&plist);
    SLTPrint(plist);

    SLTNode* find = SLTFind(plist, 3);
    SLTInsertAfter(find, 9);
    SLTPrint(plist);
    SLTPopFront(&plist);
    SLTPrint(plist);
    SLTEraseAfter(find);
    SLTDestroy(&plist);
    SLTPrint(plist);
}

int main() {
    Test02();
    return 0;
}

目录

  1. 单链表
  2. 1. 链表的概念及结构
  3. 2. 单链表的实现
  4. 1. 定义结点
  5. 2. 打印数据
  6. 3. 申请新的节点
  7. 4. 尾插
  8. 5. 头插
  9. 6. 尾删
  10. 7. 头删
  11. 8. 查找
  12. 9. 指定位置之前插入
  13. 10. 指定位置后插入
  14. 11. 指定位置前删除
  15. 12. 指定位置后删除
  16. 13. 链表的销毁
  17. 3. 程序源码
  • 💰 8折买阿里云服务器限时8折了解详情
  • 💰 8折买阿里云服务器限时8折购买
  • 🦞 5分钟部署阿里云小龙虾了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • 扩散模型(Diffusion Model)原理与图像生成实战
  • 交换排序详解:冒泡优化与快速排序四种实现
  • QClaw 本地 AI 助手接入微信的使用指南
  • AIOps 实践:使用 Dify+LangBot 搭建飞书智能体机器人
  • FPGA 实现 OV5640 摄像头视频图像显示
  • B 站 PC 端 Web 自动开启字幕脚本(2025 新版适配)
  • 垃圾回收中的可达性分析算法详解
  • Apache IoTDB 产品介绍与 Kubernetes 1.24 集群安装部署
  • 大模型技术系统学习书单推荐:从理论到实践
  • 设计模式:策略模式实现算法解耦与灵活扩展
  • C++ 二叉搜索树原理与增删查实现详解
  • 生成式大模型安全评估白皮书:核心观点与技术框架解析
  • Python 爬虫实战:爬取国产电视剧信息并构建评分数据集
  • 检索增强生成(RAG)技术最佳实践与构建指南
  • Windows 本地部署 Ollama 与 OpenClaw 构建 AI 工作流
  • 双指针算法:单调性优化容器盛水问题
  • 2024 年大厂 AI 大模型核心面试题与深度解析
  • Vue Print Designer 前端可视化打印设计器方案
  • Java 模拟算法题目练习
  • C++ string 类模拟实现

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如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