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

C/C++数据结构之循环链表

概述

        循环链表本质上也是一个单向或双向链表,但其最后一个节点的指针并不指向NULL,而是指向链表的第一个节点,从而形成一个闭合的环。这种结构使得在遍历链表时,可以从任意一个节点开始,并最终回到起始点。

        音乐播放软件一般都提供了重复播放的功能,这意味着:当播放列表中的最后一首歌曲播放完毕后,自动跳转至第一首歌曲继续播放。这种功能可以通过循环链表来轻松实现,其中每首歌曲代表链表中的一个节点。可以看到,循环链表非常适合需要重复访问元素的场景,比如:循环队列、时间轮等。

基本操作

        与单向链表类似,单向循环链表的每个节点包含两部分:一部分是存储数据的数据域,另一部分是指向下一个节点的指针。只不过最后一个节点的pNext指针不为NULL,而是指向头节点。

        对单向循环链表进行插入操作时,如果插入位置在中间,则与单向链表的插入逻辑基本相同。如果在头部插入新节点,则新节点的pNext指向当前头节点,尾部节点的pNext指向新节点,然后更新头指针指向新节点。如果链表为空,则新节点的pNext也指向自己。如果在尾部插入新节点,则首先找到链表的尾节点,即pNext指针指向头节点的那个节点;然后,让它的pNext指向新节点,新节点的pNext指向头节点。

        对单向循环链表进行删除操作时,根据要删除的节点位置,调整前一个节点的pNext指针指向被删节点的下一个节点。如果是删除头节点,则需要更新头指针。

        对单向循环链表进行遍历操作时,可以从任意节点开始,沿着pNext指针移动,直到再次到达起始节点为止。

        具体的实现,可参考下面的示例代码。双向循环链表与双向链表的区别,与上面基本类似,这里就不再赘述了。

#include <iostream>
using namespace std;struct Node
{int nData;Node* pNext;
};// 创建新节点
Node* CreateNode(int nData)
{Node* pNode = new Node();pNode->nData = nData;pNode->pNext = NULL;return pNode;
}// 查找前一个节点
Node* FindPrevious(Node* pHead, Node* pTarget)
{if (pHead == NULL || pTarget == NULL){return NULL;}Node* pCurrent = pHead;while (pCurrent->pNext != pTarget){pCurrent = pCurrent->pNext;if (pCurrent == pHead){// 循环了一圈没找到return NULL;}}return pCurrent;
}// 头部插入
void InsertAtHead(Node*& pHead, int nData)
{Node* pNode = CreateNode(nData);if (pHead == NULL){pHead = pNode;// 循环指向自己pNode->pNext = pHead;}else{// 新节点的pNext指向当前头节点pNode->pNext = pHead;// 尾部节点的pNext指向新节点Node *pPre = FindPrevious(pHead, pHead);if (pPre != NULL){pPre->pNext = pNode;}pHead = pNode;}
}// 尾部插入
void InsertAtTail(Node*& pTail, int nData)
{Node* pNode = CreateNode(nData);if (pTail == NULL){pTail = pNode;pNode->pNext = pTail;}else{pNode->pNext = pTail->pNext;pTail->pNext = pNode;// 更新尾节点为新节点pTail = pNode;}
}// 指定位置后插入
bool InsertAfter(Node* pNode, int nData)
{if (pNode == NULL){return false;}Node* pNewNode = CreateNode(nData);pNewNode->pNext = pNode->pNext;pNode->pNext = pNewNode;return true;
}// 删除操作
bool DeleteNode(Node*& pHead, Node* pDelNode)
{if (pHead == NULL || pDelNode == NULL){return false;}// 只有一个节点的情况if (pDelNode->pNext == pDelNode){delete pDelNode;pHead = NULL;return true;}// 找到前一个节点Node* pPrev = FindPrevious(pHead, pDelNode);if (pPrev == NULL){return false;}// 更新指针pPrev->pNext = pDelNode->pNext;// 如果删除的是头节点,更新head指针if (pDelNode == pHead){pHead = pDelNode->pNext;}delete pDelNode;return true;
}// 遍历操作
void TraverseList(Node* pNode)
{if (pNode == NULL){cout << "List is empty" << endl;return;}Node* pCurrent = pNode;do{cout << pCurrent->nData << " -> ";pCurrent = pCurrent->pNext;} while (pCurrent != pNode);cout << "(back to head)" << endl;
}int main()
{Node* pHead = NULL;Node* pTail = NULL;InsertAtTail(pTail, 77);pHead = pTail;InsertAtTail(pTail, 88);InsertAtHead(pHead, 66);InsertAfter(pTail, 99);// 输出:66 -> 77 -> 88 -> 99 -> (back to head)TraverseList(pHead);return 0;
}

总结

        循环链表是一种非常有用的数据结构,它通过改变传统链表最后一个节点的指针指向,形成了一个闭环,使得数据的循环访问变得简单而高效。

http://www.xdnf.cn/news/18825.html

相关文章:

  • Redis详解--基本篇
  • 手写MyBatis第31弹-用工厂模式重构MyBatis的SqlSession创建过程
  • 数据可视化——matplotlib库
  • Rust Web开发指南 第三章(Axum 请求体解析:处理 JSON、表单与文件上传)
  • IQC、IPQC、PQC、FQC、OQC在ERP/MES/WMS中的系统协同
  • [每周一更]-(第157期):深入理解Go语言的垃圾回收机制:调优与监控
  • C++ 容器——vector
  • 第2章:幽灵协议初现
  • 通过API接口多并发采集数据的方法与实践
  • 马斯克宣布开源Grok 2.5:非商业许可引争议,模型需8×40GB GPU运行,Grok 3半年后开源
  • 新的 Gmail 网络钓鱼攻击利用 AI 提示注入来逃避检测
  • VScode设置鼠标滚轮调节代码
  • 深度学习部署实战 Ubuntu24.04单机多卡部署ERNIE-4.5-VL-28B-A3B-Paddle文心多模态大模型(详细教程)
  • LeetCode-542. 01 矩阵
  • 数据库的基本操作
  • 16、web应用系统分析语设计
  • 构建AI智能体:十二、给词语绘制地图:Embedding如何构建机器的认知空间
  • 基于Langchain框架的DeepSeek-v3+Faiss实现RAG知识问答系统(含完整代码)
  • 华为云Stack环境中计算资源,存储资源,网络资源发放前的准备工作(上篇)
  • wpf之Grid控件
  • 鸿蒙分布式计算实战:用 ArkTS+Worker 池落地可运行任务管理 Demo,从单设备到跨设备全方案
  • 07-分布式能力与多设备协同
  • JDBC入门
  • DAY 55 序列预测任务介绍
  • 小红书自动评论插件
  • JUC之并发容器
  • 深度学习与自动驾驶中的一些技术
  • Java基础(十四)分布式
  • KingBase数据库迁移利器:KDTS工具深度解析与实战指南
  • golang6 条件循环