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

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

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

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

文章配图

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);

 ;

 ;

 ;

 ;

ListNode* ;

 ;

 ;
// 双向链表尾插
void
ListPushBack
(ListNode* plist, LTDataType x)
// 双向链表尾删
void
ListPopBack
(ListNode* plist)
// 双向链表头插
void
ListPushFront
(ListNode* plist, LTDataType x)
// 双向链表头删
void
ListPopFront
(ListNode* plist)
// 双向链表查找
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. 任意位置插入删除

更多推荐文章

查看全部
  • 使用 Docker Compose 快速启动 LLaMA-Factory,实现多卡 GPU 并行训练
  • Python 爬虫开发入门:从基础原理到实战应用
  • Flutter 三方库 webkit_inspection_protocol 鸿蒙化适配指南
  • 如何在 Cursor 中使用 MCP 服务
  • 前端通用 AI Rules 定义:适配 Cursor、Trae 等主流工具
  • 前端 pnpm workspace 架构详解
  • Spring MVC 响应处理:页面、数据与状态码配置详解
  • Linux 远程服务器直接下载 HuggingFace 模型与数据集
  • LLM 论文淘金:大模型精选论文解读与实用建议
  • GitHub 上 10 个适合新手入门的 Python 教程与项目推荐
  • 基于物理机理引导和自编码器融合的机械早期故障诊断
  • C++ 实现 UTF-8 与 GBK 字符编码转换及源码示例
  • AIGC 技术演进与核心应用解析
  • 详解 Python 数据处理库 Pandas 使用方法
  • Windows 10/11 下 WSL2 安装 Ubuntu 20.04 及 D 盘迁移指南
  • Python 量化回测框架 backtesting.py 实战教程
  • Android 开发常见知识点与最佳实践整理
  • 芭堤雅中文离线地图 App 发布,支持离线导航与酒店查询
  • AI 无人机智慧巡检平台:20+ 场景识别与全流程调度方案
  • 推荐系统核心算法与架构解析:从协同过滤到大模型应用

相关免费在线工具

  • 加密/解密文本

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