跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

数据结构核心:链表(单链表、双链表及循环链表)

链表作为线性表的链式存储结构,涵盖单链表、双链表、循环链表及静态链表等形态。深入解析各类链表的结点定义、初始化、插入、删除及查找等核心操作实现细节,对比分析了顺序表与链表在存取效率、空间管理及适用场景上的差异,旨在帮助开发者理解底层数据结构原理与工程权衡。

灵魂摆渡发布于 2026/3/24更新于 2026/8/2339 浏览
数据结构核心:链表(单链表、双链表及循环链表)

链表结构详解

一、单链表的定义

单链表是线性表的链式存储结构。它通过一组任意的存储单元来存储数据元素,并通过指针建立数据元素之间的关系。每个结点包含数据域和指针域,其中指针域指向后继结点的地址。

typedef struct LNode {
    ElemType data;      // 数据域
    struct LNode* next; // 指针域
} LNode, *LinkList;

注意区分 LNode*(表示一个结点)与 LinkList(表示整个链表)。由于元素在内存中离散分布,单链表不支持随机存取,查找特定元素需从头遍历。

头节点与头指针

无论是否有头节点,头指针始终指向链表的第一个结点。头结点是带头链表中的第一个结点,通常不存储有效信息。引入头结点的优势在于统一了空表和非空表的处理逻辑,且第一个数据结点的位置操作与其他位置一致。

二、单链表的基本操作

1. 初始化

带头结点: 申请一块内存作为头结点,将 next 指针置为 NULL。

bool InitList(LinkList& L) {
    L = (LNode*)malloc(sizeof(LNode));
    if (!L) return false;
    L->next = NULL;
    return true;
}

不带头结点: 直接将头指针设为 NULL。

bool InitList(LinkList& L) {
    L = NULL;
    return true;
}

2. 判空

检查头指针的 next 是否为 NULL。若为空则返回 true。

bool Empty(LinkList L) {
    return L->next == NULL;
}

3. 求表长

遍历链表统计数据结点个数。

int Length(LinkList L) {
    int len = 0;
    LNode* p = L;
    while (p->next != NULL) {
        p = p->next;
        len++;
    }
    return len;
}

4. 按序号查找结点

从表头开始遍历,找到第 i 个结点。若越界或链表过短则返回 NULL。

LNode* GetElem(LinkList L, int i) {
    LNode* p = L;
    int j = 0;
    while (p != NULL && j < i) {
        p = p->next;
        j++;
    }
    return p;
}

5. 按值查找表结点

遍历链表寻找第一个值为 e 的结点。

LNode* LocateElem(LinkList L, ElemType e) {
    LNode* p = L->next;
    while (p != NULL && p->data != e)
        p = p->next;
    return p;
}

6. 插入结点操作

指定位置插入: 先定位到第 i-1 个结点,再执行指针修改。

bool ListInsert(LinkList& L, int i, ElemType e) {
    if (i < 1) return false;
    LNode* p = L;
    int j = 0;
    while (p != NULL && j < i - 1) {
        p = p->next;
        j++;
    }
    if (p == NULL) return false;
    
    LNode* s = (LNode*)malloc(sizeof(LNode));
    s->data = e;
    s->next = p->next;
    p->next = s;
    return true;
}

指定结点前插: 利用已知结点 p,在其后插入新结点并交换数据,避免重新遍历。

bool InsertPriorNode(LNode* p, ElemType e) {
    if (p == NULL) return false;
    LNode* s = (LNode*)malloc(sizeof(LNode));
    s->next = p->next;
    p->next = s;
    s->data = p->data;
    p->data = e;
    return true;
}

指定结点后插: 直接在 p 之后插入新结点。

bool InsertNextNode(LNode* p, ElemType e) {
    if (p == NULL) return false;
    LNode* s = (LNode*)malloc(sizeof(LNode));
    s->data = e;
    s->next = p->next;
    p->next = s;
    return true;
}

7. 删除结点操作

定位到第 i-1 个结点,释放第 i 个结点内存。

bool ListDelete(LinkList& L, int i, ElemType& e) {
    LNode* p = L;
    int j = 0;
    while (p->next != NULL && j < i - 1) {
        p = p->next;
        j++;
    }
    if (p->next == NULL || j > i - 1) return false;
    
    LNode* q = p->next;
    e = q->data;
    p->next = q->next;
    free(q);
    return true;
}

8. 建立单链表

头插法: 每次新结点插在头结点之后,生成的链表顺序与输入顺序相反。

LinkList List_HeadInsert(LinkList& L) {
    L = (LNode*)malloc(sizeof(LNode));
    L->next = NULL;
    int x;
    scanf("%d", &x);
    while (x != 9999) {
        LNode* s = (LNode*)malloc(sizeof(LNode));
        s->data = x;
        s->next = L->next;
        L->next = s;
        scanf("%d", &x);
    }
    return L;
}

尾插法: 维护尾指针 r,新结点插在尾部,保持输入顺序。

LinkList List_TailInsert(LinkList& L) {
    L = (LNode*)malloc(sizeof(LNode));
    LNode* r = L;
    int x;
    scanf("%d", &x);
    while (x != 9999) {
        LNode* s = (LNode*)malloc(sizeof(LNode));
        s->data = x;
        r->next = s;
        r = s;
        scanf("%d", &x);
    }
    r->next = NULL;
    return L;
}

三、双链表的定义

双链表在单链表基础上增加了前驱指针 prior,可双向遍历。结点结构如下:

typedef struct DNode {
    ElemType data;
    struct DNode* prior, * next;
} DNode, *DLinklist;

双链表插入与删除操作的时间复杂度可优化至 O(1),因为可以直接访问前驱结点。

四、双链表的基本操作

1. 初始化

申请头结点,将 prior 和 next 均置为 NULL。

bool InitDLinklist(DLinklist& L) {
    L = (DNode*)malloc(sizeof(DNode));
    if (!L) return false;
    L->prior = NULL;
    L->next = NULL;
    return true;
}

2. 插入结点

在已知结点 p 后插入结点 s,需同时调整前后指针关系。

bool InsertNextDNode(DNode* p, DNode* s) {
    if (!p || !s) return false;
    s->next = p->next;
    if (p->next) p->next->prior = s;
    s->prior = p;
    p->next = s;
    return true;
}

3. 删除结点

删除结点 p 的后继结点,需断开前后连接并释放内存。

bool DeleteNextNode(DNode* p) {
    if (!p) return false;
    DNode* q = p->next;
    if (!q) return false;
    p->next = q->next;
    if (q->next) q->next->prior = p;
    free(q);
    return true;
}

4. 销毁链表

循环删除所有数据结点,最后释放头结点。

void DestroyList(DLinklist& L) {
    while (L->next != NULL)
        DeleteNextNode(L);
    free(L);
    L = NULL;
}

五、循环链表

1. 循环单链表

表尾结点的 next 指针指向头结点,形成环状。判空条件变为 L->next == L。

2. 循环双链表

头结点的 prior 指针也指向表尾结点。判空需同时检查 next 和 prior 是否指向头指针。

六、静态链表

使用数组模拟链式存储,next 字段存储数组下标而非地址。适合无法动态分配内存的环境,但长度固定。

#define MaxSize 50
typedef struct {
    ElemType data;
    int next;
} SLinkList[MaxSize];

以 -1 作为结束标志。

七、顺序表和链表的区别

特性顺序表链表
存取方式随机存取 O(1)顺序存取 O(n)
物理结构连续存储离散存储
空间分配需预分配,扩容成本高动态分配,利用碎片空间
插入/删除需移动大量元素仅需修改指针

顺序表适合频繁查询的场景,而链表更适合频繁插入删除且长度不确定的场景。

目录

  1. 链表结构详解
  2. 一、单链表的定义
  3. 头节点与头指针
  4. 二、单链表的基本操作
  5. 1. 初始化
  6. 2. 判空
  7. 3. 求表长
  8. 4. 按序号查找结点
  9. 5. 按值查找表结点
  10. 6. 插入结点操作
  11. 7. 删除结点操作
  12. 8. 建立单链表
  13. 三、双链表的定义
  14. 四、双链表的基本操作
  15. 1. 初始化
  16. 2. 插入结点
  17. 3. 删除结点
  18. 4. 销毁链表
  19. 五、循环链表
  20. 1. 循环单链表
  21. 2. 循环双链表
  22. 六、静态链表
  23. 七、顺序表和链表的区别
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • 渗透测试中利用 403 状态码绕过访问限制的方法
  • MCP 教程:将 Figma 设计稿转化为前端代码
  • 2026 AI Agent 产品横评:OpenClaw 及 8 大主流版本对比与场景推荐
  • 智能客服机器人实战:基于 NLP 与微服务架构的高并发解决方案
  • MySQL 与 MCP 集成:从环境构建到 AI 数据交互全流程
  • 算法的本质:现代视角下的深度解析
  • AI Agent 新范式:FastGPT+MCP 协议实现工具增强型智能体构建
  • Kafka 事务:构建可靠的分布式消息处理系统
  • GitHub 教育优惠及 Copilot Pro 申请全流程指南
  • Python 基于 itertools 生成器的量子计算模拟技术
  • OpenClaw 本地部署飞书机器人配置指南
  • 本地大模型运行工具 Ollama 安装与使用指南
  • 2026 年 Python+AI 学习路线完整指南:从零基础到实战专家
  • 微服务项目在线 OJ 系统:JWT 令牌与网关身份认证实现
  • 动态规划:最小路径和问题解析
  • Java AOP 技术详解
  • 2025 年主流 AI 写作工具横向对比与选择指南
  • 从 Tomcat 到 TONGWEB:迁移指南与性能对比
  • AI 基础核心概念:Prompt、Agent、Function Calling 及 RAG 解析
  • Linux 进程间通信实战:命名管道(FIFO)详解

相关免费在线工具

  • 加密/解密文本

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