当前位置: 首页 > news >正文

数据结构:循环链表详解(从原理到实战,新手必看)

在链表家族中,除了最基础的单链表双链表,还有一种结构非常特殊且实用的类型 ——循环链表。它将单链表的首尾相连,让整个链表形成一个闭环,解决了普通单链表 “走到末尾就结束” 的局限,在环形缓冲区、约瑟夫环、操作系统时间片轮转等场景中被大量使用。

本文将从概念、结构特点、核心操作、C 语言完整实现应用场景,带你彻底掌握循环链表。

一、什么是循环链表?

循环链表(Circular Linked List)是单链表的一种变体

  • 普通单链表:尾节点的next指针指向NULL
  • 循环链表:尾节点的next指针重新指向头节点,整个链表形成一个环

核心特点

  1. 链表无明显的头和尾,从任意节点出发都能遍历全表
  2. 可以不使用头结点,也可以带哨兵头结点(更常用)
  3. 遍历时终止条件不再是p == NULL,而是p == head
  4. 插入、删除逻辑与单链表类似,但边界判断更简洁

二、循环链表的结构体定义(C 语言)

我们使用带头结点的循环链表,方便统一处理空表和非空表逻辑。

#include <stdio.h> #include <stdlib.h> #include <assert.h> // 节点结构体 typedef struct Node { int data; // 数据域 struct Node *next; // 指针域 } Node; // 链表类型重定义(指向头结点) typedef Node* CList;

三、初始化循环链表

创建一个头结点,并让头结点自环,代表空链表。

// 初始化循环链表 void InitList(CList *head) { *head = (Node*)malloc(sizeof(Node)); assert(*head != NULL); // 自环,空循环链表 (*head)->next = *head; }

四、插入操作(尾插法)

尾插是循环链表最常用的插入方式,找到尾节点后插入新节点,并保持环结构。

// 尾插法插入元素 bool InsertTail(CList head, int val) { assert(head != NULL); Node *newNode = (Node*)malloc(sizeof(Node)); assert(newNode != NULL); newNode->data = val; newNode->next = head; // 新节点指向头结点 // 找尾节点 Node *p = head; while (p->next != head) { p = p->next; } p->next = newNode; return true; }

五、遍历循环链表

因为是环,不能用 p == NULL 结束,而是回到头结点就停止。

// 遍历打印循环链表 void ShowList(CList head) { assert(head != NULL); Node *p = head->next; while (p != head) { printf("%d ", p->data); p = p->next; } printf("\n"); }

六、删除指定值的节点

找到待删节点的前驱,修改指针,保持环不断裂。

// 删除第一个值为 val 的节点 bool DeleteVal(CList head, int val) { assert(head != NULL); Node *prev = head; Node *cur = head->next; while (cur != head) { if (cur->data == val) { prev->next = cur->next; free(cur); return true; } prev = cur; cur = cur->next; } // 没找到 return false; }

七、判断链表是否为空

bool IsEmpty(CList head) { return head->next == head; }

八、销毁循环链表

因为是环,要逐个节点释放,最后释放头结点。

void DestroyList(CList *head) { if (*head == NULL) return; Node *p = (*head)->next; Node *q = NULL; while (p != *head) { q = p->next; free(p); p = q; } free(*head); *head = NULL; }

九、经典应用场景

  1. 约瑟夫环问题循环链表最经典考题:n 个人围成一圈,报数到 m 出局,求最后幸存者。

  2. 操作系统时间片轮转调度多个进程形成环,CPU 轮流分配时间片。

  3. 环形缓冲区(Ring Buffer)串口、音频、网络数据缓存。

  4. 游戏技能冷却、回合制游戏循环触发、循环检测。


十、总结

循环链表本质就是首尾相连的单链表,结构简单但思想非常实用:

  • 核心:尾节点next = head
  • 遍历:p != head作为结束条件
  • 操作逻辑与单链表几乎一致,更容易处理环形问题
  • 面试高频:约瑟夫环、循环链表判断、环形队列
http://www.jsqmd.com/news/524177/

相关文章:

  • 如何快速上手DirectX Shader Compiler:10个实用技巧帮你高效编译HLSL
  • 计算机毕业设计springboot基于的农业无人机培训考试系统 基于SpringBoot的智慧农业无人机技能培训与考核平台设计与实现 基于SpringBoot的农用无人机操作员培训认证系统设计与实现
  • 别光重启了!深度拆解苍穹外卖项目Nginx配置与后端端口映射的联调逻辑
  • Zotero文献条目如何自定义显示年份等关键信息?
  • 人工智能|计算机视觉——微表情识别(Micro expression recognition)的研究现状
  • 如何高效为udacity-nanodegrees项目贡献课程更新:新手友好的完整指南
  • 从山东大学考题看机器学习核心概念:线性回归、朴素贝叶斯与SVM详解
  • 告别英文界面:GitHub Desktop汉化实战教程(含常见问题解决)
  • 一次网络故障复盘:为什么SPF算法重新计算后,我的流量路径变了?
  • 告别等待!SpringBoot + WebFlux + WebSocket 三件套搞定OpenAI流式对话(附完整代码)
  • Hanami框架从1.x到2.x的完整迁移指南:终极升级策略
  • 避开网络坑:SpaCy模型下载的3种方法对比(pip/conda/离线包)
  • Nacos安全漏洞实战:从环境搭建到漏洞复现的完整指南(含避坑技巧)
  • AI浪潮下的22个新职业:高薪诱惑背后,你真的能抓住吗?
  • NestJS + TypeORM实战:从零搭建一个用户管理系统(附完整代码)
  • 深度强化学习分布式训练终极指南:CleanRL多进程环境并行采样架构详解
  • 手把手教你从GitHub克隆并运行LiveCharts2官方示例(Avalonia UI环境)
  • Linux日志转发:rsyslog UDP配置实战指南,一键打通日志通道!
  • 10分钟快速上手express-graphql:构建你的第一个GraphQL API服务器
  • Open UI5 源代码解析之695:CarouselLayout.js
  • 计算机毕业设计springboot基于的企业采购系统设计与实现 基于SpringBoot的智慧企业供应链采购管理平台设计与实现 基于SpringBoot的数字化企业物资采购协同系统设计与实现
  • 从零到一:在飞牛云fnOS上,用1Panel与Halo打造你的专属技术博客
  • Sizzle选择器引擎终极指南:React、Vue、Angular集成实战
  • PARL框架扩展与二次开发:高级API与底层原理深度剖析
  • P5264 多项式三角函数
  • 漏洞分析-浪潮GS企业管理软件远程代码执行漏洞实战解析
  • 工业称重设备选型指南:四川柯力电测以全系列产品与系统化能力满足多元场景需求 - 深度智识库
  • 2026年陕西TVC广告拍摄与短视频内容力观察:西安铿锵如何以影像策略驱动品牌高效传播 - 深度智识库
  • 终极移动端数据架构指南:LitePal与Firebase Firestore的本地云端数据同步策略
  • 告别盲目调管子!用gm/ID方法在Cadence Virtuoso里搞定模拟IC设计(以smic13mmrf工艺为例)