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

数据结构:带头双向循环链表的定义与实现

综述由AI生成详细讲解了带头双向循环链表的定义、结构特点及核心接口实现。内容包括头插、头删、尾插、尾删及任意位置插入删除操作。通过与无头单向非循环链表对比,阐述了带头双向循环链表在简化操作方面的优势。提供了完整的 C 语言代码示例,涵盖节点结构体定义、内存分配及指针维护逻辑。

莫名其妙发布于 2026/3/28更新于 2026/5/2629 浏览
数据结构:带头双向循环链表的定义与实现

文章配图

1. 链表的分类

链表有多种分类,单链表是结构最简单的链表之一,但使用较为繁琐。根据是否带头、单向或双向、循环或非循环,链表可分为多种结构。我们重点讲解两种极端情况:无头单向非循环链表(结构最简单,使用最麻烦)和带头双向循环链表(结构最复杂,使用最简单)。掌握这两种链表后,其他链表的使用将游刃有余。

无头单向非循环链表(单链表):

文章配图

带头双向循环链表:

文章配图

2. 带头双向循环链表

下面对带头双向循环链表的相关接口进行实现。

接口声明

首先定义头文件,包含结构体声明和各种接口:

typedef int LTDataType;

typedef struct ListNode {
    struct ListNode* prev;
    struct ListNode* next;
    LTDataType data;
} ListNode;

// 创建返回链表的头结点
ListNode* ListCreate();
// 创建一个新节点
ListNode* BuyListNode();
// 双向链表销毁
void ListDestory(ListNode* plist);
// 双向链表打印
void ListPrint(ListNode* plist);
// 双向链表尾插
void ListPushBack(ListNode* plist, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* plist);
// 双向链表头插
void ListPushFront(ListNode* plist, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* plist);
// 双向链表查找
ListNode* ListFind(ListNode* plist, LTDataType x);
// 双向链表在 pos 的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除 pos 位置的结点
void ListErase(ListNode* pos, ListNode* phead);

结构体定义了节点存储的数据、指向下一个节点的地址以及指向上一个节点的地址。能存储上一个节点的地址是它与单链表最重要的区别,这使得使用过程相对简单。接下来将对各个接口进行实现。

头节点创建

由于带头节点,首先需要设置头节点:

// 创建一个头节点
ListNode* ListCreate() {
    ListNode* head = (ListNode*)malloc(sizeof(ListNode));
    if (head == NULL) {
        printf("开辟节点失败");
        return NULL;
    }
    head->next = head;
    head->prev = head;
    return head;
}

头节点分配一块空间,让 next 和 prev 都指向它自己,满足带头双向循环链表的要求。

节点创建

无论是头删头插、尾删尾插还是任意位置插删,都需要节点。建立接口专门向内存申请空间来建立新节点。

头插头删

// 双向链表头插
void ListPushFront(ListNode* plist, LTDataType x) {
    assert(plist);
    ListNode* next = plist->next;
    ListNode* NewNext = BuyListNode();
    NewNext->data = x;
    NewNext->next = plist->next;
    NewNext->prev = plist;
    plist->next = NewNext;
    next->prev = NewNext;
}

// 双向链表头删
void ListPopFront(ListNode* plist) {
    assert(plist);
    assert(plist->next != plist);
    ListNode* next = plist->next;
    ListNode* NewNext = next->next;
    NewNext->prev = plist;
    plist->next = NewNext;
    free(next);
    next = NULL;
}

头插头删逻辑清晰。头插时,将头节点的 next 放入要插入节点的地址,插入节点的 prev 指向头节点,头节点的下一个节点的 prev 指向所插入的节点,所插入节点的 next 指向该节点。由于有头节点,即使没有其它节点,头节点的 next 也指向自己,形成闭环,不会出错。头删同理,维护好各节点的 next 和 prev 即可。

尾插尾删

// 双向链表尾插
void ListPushBack(ListNode* plist, LTDataType x) {
    assert(plist);
    ListNode* tail = plist->prev;
    ListNode* NewTail = BuyListNode();
    NewTail->data = x;
    NewTail->prev = plist->prev;
    tail->next = NewTail;
    NewTail->next = plist;
    plist->prev = NewTail;
}

// 双向链表尾删
void ListPopBack(ListNode* plist) {
    assert(plist);
    assert(plist->next != plist);
    ListNode* tail = plist->prev;
    ListNode* NewTail = tail->prev;
    plist->prev = NewTail;
    NewTail->next = plist;
    free(tail);
    tail = NULL;
}

尾插尾删逻辑与头插头删类似。需注意如果没有头节点之外的节点则不要删除,因此在接口中加入 assert(plist->next != plist),防止删除头节点。

任意位置插入删除

// 双向链表在 pos 的前面进行插入
void ListInsert(ListNode* pos, LTDataType x) {
    assert(pos);
    ListNode* prev = pos->prev;
    ListNode* NewPrev = BuyListNode();
    NewPrev->data = x;
    NewPrev->next = pos;
    NewPrev->prev = prev;
    prev->next = NewPrev;
    pos->prev = NewPrev;
}

// 双向链表删除 pos 位置的结点
void ListErase(ListNode* pos, ListNode* phead) {
    assert(pos);
    assert(pos != phead);
    ListNode* prev = pos->prev;
    ListNode* next = pos->next;
    prev->next = next;
    next->prev = prev;
    free(pos);
    pos = NULL;
}

不能把头节点删除,因此有 assert(pos != phead)。如果不需要断言,任意位置删除只需要把 pos 传过来即可。

目录

  1. 1. 链表的分类
  2. 2. 带头双向循环链表
  3. 接口声明
  4. 头节点创建
  5. 节点创建
  6. 头插头删
  7. 尾插尾删
  8. 任意位置插入删除
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • Stable Diffusion 模型原理与本地部署实践
  • 滑动窗口算法详解:解决子数组和子串问题
  • DeepSeek 与通义万相结合实现 AI 视频生成流程指南
  • Python 量化投资系统构建指南
  • Windows 11 下 WSL Ubuntu 安装与配置实战
  • GRPO 算法损失函数原理与代码实现
  • GCC 14 编译选项配置与 C++ 高性能构建指南
  • 从模板到反射:C++26 泛型编程进阶之路
  • 深入理解 C 语言数组的内存布局与访问
  • VSCode 中 GitHub Copilot 大模型体系、订阅策略与 Agent 机制
  • C++ STL 容器 string 的遍历方法
  • DeepFace + OpenCV 实现实时情绪分析器
  • 基于 Windows 服务器部署微信接入 DeepSeek AI 聊天机器人
  • Kimi K2.5 实测:多模态与编程能力能否兼得
  • Apache Arrow FFI 接口详解:C 与 Rust 数据零拷贝交互
  • MySQL 运维实战:常见问题排查与解决方案
  • GitHub 日榜:AI 工具链与工程化实践热点
  • 前端安全实战:密码加密、XSS 与 CSRF 防护指南
  • Flutter Web 开发:构建跨平台 Web 应用
  • DeepSeek 深度使用指南:提示词技巧与本地知识库搭建

相关免费在线工具

  • 加密/解密文本

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