C++开发基础之循环链表详解含示例

1. 引言

循环链表的概念和特点

循环链表是一种链表的变体,它与普通链表最大的不同是:在循环链表中,最后一个节点的指针不是NULL,而是指向头节点,形成了一个环。这种特殊结构使得循环链表中的数据可以像环一样循环访问。

循环链表的特点如下:

  1. 可以循环使用存储空间,实现数据的重复利用。
  2. 可以很方便地遍历整个链表,因为从任意一个节点出发都能遍历到所有节点。
  3. 插入和删除操作比较容易,因为在任意一个节点处进行插入和删除操作都是类似的。

循环链表与普通链表的区别

循环链表和普通链表的最大区别在于最后一个节点的指针。在普通链表中,最后一个节点的指针指向NULL,表示链表的结尾;而在循环链表中,最后一个节点的指针指向头节点,形成了一个环。

这种环的结构使得循环链表可以像环一样循环访问其中的数据。也就是说,无论从哪个节点开始遍历,都可以遍历到所有节点。而在普通链表中,只能从头节点开始遍历,直到遍历到NULL为止。

此外,循环链表插入和删除节点的操作比较容易,因为在任意一个节点处进行插入和删除操作都是类似的。而在普通链表中,对于头节点和尾节点的插入和删除操作需要进行特殊处理。

综上所述,循环链表的环状结构使得它具有一些特殊的性质,例如可以循环访问其中的数据、插入和删除节点的操作比较容易等等。

2. 基本结构

定义循环链表的结构

循环链表的结构可以通过定义一个节点结构体来实现。每个节点包含两个主要成员:数据和指向下一个节点的指针。

以下是一个示例的循环链表节点结构的定义:

struct Node {
    int data;         // 节点中存储的数据
    Node* next;       // 指向下一个节点的指针
};

在循环链表中,需要注意的一点是最后一个节点的指针不是NULL,而是指向头节点,形成了一个环。因此,在创建循环链表时,需要特别处理最后一个节点的指针,使其指向头节点。

另外,为了方便操作循环链表,还可以定义一个指向头节点的指针,以便快速访问和操作循环链表的各个节点。

Node* head = nullptr;  // 指向循环链表头节点的指针

描述循环链表的节点和指针关系

循环链表的节点和指针关系可以用mermaid语言来绘制图示。以下是一个简单的例子,由三个节点组成,节点分别为A、B和C。

节点A
节点B
节点C

在这个例子中,每个节点用一个方框表示,方框内包含一个数据字段。节点之间用箭头表示指针关系,箭头从指向前驱节点的指针指向后继节点。

需要注意的是,最后一个节点的指针指向头节点,以形成循环。

3.基本操作

1. 初始化循环链表

初始化一个循环链表需要定义一个头节点,并将其指向自身,以形成一个空的循环链表。在C++中,可以使用如下代码进行初始化:

Node* head = new Node();
head->next = head;
2. 在头部插入元素

在循环链表的头部插入元素,需要把新的节点插入到头节点和第一个节点之间,并修改头节点的指针指向新的节点。具体操作如下:

Node* newNode = new Node();
newNode->data = val;
newNode->next = head->next;
head->next = newNode;
3. 在尾部插入元素

向循环链表的尾部插入元素,需要找到最后一个节点,并将其指针指向新的节点。具体操作如下:

// 找到最后一个节点
Node* last = head;
while (last->next != head) {
    last = last->next;
}

// 创建新的节点
Node* newNode = new Node();
newNode->data = val;
newNode->next = head;

// 将最后一个节点的指针指向新的节点
last->next = newNode;

需要注意的是,在循环链表中,最后一个节点的指针不是NULL,而是指向头节点。

4. 删除指定位置的元素

在循环链表中删除指定位置的元素,需要找到要删除的节点的前驱节点,并将其指针指向要删除节点的后继节点。具体操作如下:

// 找到要删除的节点的前驱节点
Node* prev = head;
for (int i = 0; i < pos - 1; i++) {
    prev = prev->next;
}

// 删除节点
Node* temp = prev->next;
prev->next = temp->next;
delete temp;

需要注意的是,如果要删除的是最后一个节点,需要特殊处理其指针指向头节点。

5. 遍历循环链表

遍历循环链表可以使用循环来实现,从头节点开始,依次遍历每个节点,并输出其中的数据。具体操作如下:

Node* p = head->next;
while (p != head) {
    cout << p->data << " ";
    p = p->next;
}

需要注意的是,由于循环链表的最后一个节点指向头节点,因此在遍历时应该以头节点为判断条

4. 循环链表的实现

使用动态内存分配来实现循环链表,可以在每次插入新节点时使用 new 运算符来分配内存。以下是一个使用动态内存分配实现循环链表的示例代码:

#include <iostream>

struct Node {
    int data;
    Node* next;
};

void insertAtHead(Node*& head, int val) {
    Node* newNode = new Node();
    newNode->data = val;

    if (head == nullptr) {
        newNode->next = newNode;
        head = newNode;
    } else {
        newNode->next = head->next;
        head->next = newNode;
    }
}

void insertAtTail(Node*& head, int val) {
    Node* newNode = new Node();
    newNode->data = val;

    if (head == nullptr) {
        newNode->next = newNode;
        head = newNode;
    } else {
        Node* last = head;
        while (last->next != head) {
            last = last->next;
        }

        newNode->next = head;
        last->next = newNode;
    }
}

void deleteAtPosition(Node*& head, int pos) {
    if (head == nullptr) {
        return;
    }

    Node* prev = head;
    for (int i = 0; i < pos - 1; i++) {
        prev = prev->next;
    }

    Node* temp = prev->next;
    prev->next = temp->next;

    delete temp;
}

void traverse(Node* head) {
    if (head == nullptr) {
        return;
    }

    Node* p = head->next;
    while (p != head) {
        std::cout << p->data << " ";
        p = p->next;
    }
    std::cout << std::endl;
}

int main() {
    Node* head = nullptr;

    insertAtHead(head, 3);  // 往头部插入元素
    insertAtHead(head, 2);
    insertAtHead(head, 1);

    traverse(head);  // 输出链表元素:1 2 3

    insertAtTail(head, 4);  // 往尾部插入元素
    insertAtTail(head, 5);

    traverse(head);  // 输出链表元素:1 2 3 4 5

    deleteAtPosition(head, 2);  // 删除第二个位置元素

    traverse(head);  // 输出链表元素:1 2 4 5

    return 0;
}

执行结果
在这里插入图片描述
在这个示例中,Node 结构体表示循环链表的节点,其中 data 字段存储节点的值,next 字段指向下一个节点。insertAtHead 函数用于在头部插入元素,insertAtTail 函数用于在尾部插入元素,deleteAtPosition 函数用于删除指定位置的元素,traverse 函数用于遍历循环链表并输出元素。在 main 函数中演示了如何使用这些操作来创建循环链表,并进行插入、删除和遍历操作。

5. 应用场景与实际案例

循环链表在实际开发中有许多应用场景,特别适用于需要循环遍历或循环操作的情况。以下是一些常见的应用场景和实际案例:

  1. 缓存管理:在计算机系统中,缓存是一种常见的性能优化技术。通过将最近使用的数据存储在高速缓存中,可以显著提高数据访问速度。环形链表常被用作缓存的数据结构,以便实现最新数据的存储和访问。

  2. 数据处理:在实时数据处理和流式处理中,环形链表可以用来存储连续的数据流。例如,在实时监测系统中,需要对连续的传感器数据进行处理和分析。使用环形链表,可以方便地保存最近的一段时间内的数据,以便后续处理和分析。

  3. 音频处理:在音频处理领域,环形缓冲区常被用于实时音频信号的采样和处理。通过将音频数据存储在环形链表中,可以实现音频数据的循环存储,并且方便进行实时信号处理,如音频特效、混音等。

  4. 消息队列:消息队列是一种常见的通信模式,用于在分布式系统中传递消息。环形链表可以用来实现消息队列的缓冲区,确保消息按照顺序被消费,同时避免队列长度无限增长的问题。

  5. 循环赛制和任务分配:在体育竞技或任务分配中,往往需要构建一个循环的比赛或任务安排。使用环形链表可以方便地管理参与者或任务之间的关系,并实现循环的安排和分配。

循环链表在这些场景中的优势和使用方法如下:

  1. 循环遍历:由于循环链表的最后一个节点指向头节点,因此可以轻松实现循环遍历,不需要额外的判断条件。在音乐播放器的播放列表中,可以实现循环顺序播放歌曲。

  2. 动态操作:循环链表可以方便地插入和删除节点。在音乐播放器的播放列表中,可以在任意位置插入新的歌曲或删除已经播放的歌曲。

  3. 空间效率:循环链表可以节省内存空间,因为它不需要额外的指针来表示末尾节点。在约瑟夫环问题中,使用循环链表可以有效地模拟人们围成圆圈的结构。

  4. 灵活性:循环链表可以适应动态变化的需求。在音乐播放器的播放列表中,可以根据用户的操作随时调整播放顺序或删除歌曲。

6. 时间复杂度分析

循环链表的各种操作的时间复杂度如下:

  1. 插入操作:

    • 在头部插入节点:O(1)
    • 在尾部插入节点:O(n),需要遍历到最后一个节点
  2. 删除操作:

    • 删除指定位置的节点:O(n),需要遍历到指定位置的前一个节点
  3. 遍历操作:

    • 遍历整个链表:O(n),需要遍历到最后一个节点

对比循环链表与普通链表的性能差异:

  1. 插入和删除操作:循环链表在头部插入和删除节点的时间复杂度都是O(1),而在尾部插入和删除节点的时间复杂度都是O(n)。相比之下,普通链表在头部插入和删除节点的时间复杂度也是O(1),但在尾部插入和删除节点的时间复杂度为O(1),因为普通链表可以直接访问到尾节点。

  2. 遍历操作:循环链表和普通链表的遍历操作时间复杂度都是O(n),需要遍历整个链表。没有明显的性能差异。

因此,循环链表在头部插入和删除节点的操作上具有优势,时间复杂度为O(1),但在尾部插入和删除节点的操作上劣于普通链表,时间复杂度为O(n)。在遍历操作上,两者没有明显的性能差异。选择使用循环链表还是普通链表,取决于具体的应用需求和操作频率。

7. 优缺点与总结

循环链表的优点和适用情况:

  1. 循环遍历:循环链表可以轻松实现循环遍历,不需要额外的判断条件,适用于需要循环操作或循环访问的场景。

  2. 动态操作:循环链表可以方便地插入和删除节点,适用于需要频繁进行插入和删除操作的场景。

  3. 空间效率:循环链表可以节省内存空间,因为它不需要额外的指针来表示末尾节点,适用于对内存占用有限制的场景。

  4. 灵活性:循环链表可以适应动态变化的需求,可以根据需要随时调整链表结构,适用于需要灵活性的场景。

循环链表的缺点和局限性:

  1. 尾部操作低效:在循环链表中,尾部插入和删除节点的操作时间复杂度较高,为O(n),相比普通链表劣势明显。

  2. 难以确定链表长度:由于循环链表没有明确的结束标志,难以准确获取链表的长度,可能需要额外的计数器来维护。

  3. 需要特殊处理空链表:循环链表中,空链表的表示需要特殊处理,可能需要使用一个特殊节点来表示空链表。

建议选择合适的数据结构:

  1. 如果需要频繁在头部进行插入和删除操作,并且对尾部操作要求不高,可以选择循环链表。

  2. 如果需要频繁在尾部进行插入和删除操作,或者对获取链表长度有较高需求,可以选择普通链表。

  3. 如果对空间效率有限制,且需要循环遍历、动态操作和灵活性,可以考虑使用循环链表。

总之,在选择合适的数据结构时,需要根据具体应用场景的需求权衡各种因素,包括操作频率、时间复杂度、空间效率和灵活性等,以便找到最适合的数据结构。

参考文档

  • < 数据结构 > 单向环形链表
  • 双向链表、环形链表及约瑟夫问题
  • 寻找环形链表的入口点

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/366059.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【日常聊聊】开源软件影响力

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;JAVA ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 方向一&#xff1a;开源软件如何推动技术创新 方向二&#xff1a;开源软件的商业模式 方向三&#xff1a;开源软件的安全风险 方…

Java中使用StopWatch实现代码块耗时统计/计时某段代码执行

场景 Java实战-基于JDK的LRU算法实现、优雅的实现代码耗时统计(Spring AOP、AutoCloseable方式)&#xff1a; Java实战-基于JDK的LRU算法实现、优雅的实现代码耗时统计(Spring AOP、AutoCloseable方式)_lru 算法 jdk-CSDN博客 上面讲了统计方法的耗时&#xff0c;实现和使用…

单机搭建hadoop环境(包括hdfs、yarn、hive)

单机可以搭建伪分布式hadoop环境&#xff0c;用来测试和开发使用&#xff0c;hadoop包括&#xff1a; hdfs服务器 yarn服务器&#xff0c;yarn的前提是hdfs服务器&#xff0c; 在前面两个的基础上&#xff0c;课可以搭建hive服务器&#xff0c;不过hive不属于hadoop的必须部…

林浩然与杨凌芸的Java奇缘:静态关键字的恋爱三部曲

林浩然与杨凌芸的Java奇缘&#xff1a;静态关键字的恋爱三部曲 Lin Haoran and Yang Lingyun’s Java Romance: The Trilogy of Love with the Static Keyword 在编程世界里&#xff0c;有一个名叫林浩然的程序员&#xff0c;他风度翩翩&#xff0c;思维敏捷&#xff0c;对Java…

yo!这里是c++IO流相关介绍

目录 前言 C语言的输入输出 CIO流基本介绍 流的概念 IO流类库 iostream fstream stringstream 后记 前言 学过C语言的输入输出相关知识点的童鞋应该多多少少会觉得有些许麻烦&#xff0c;反正我就是这么觉得的&#xff0c;scanf、printf等函数不仅数量众多&#xff0c…

金三银四_程序员怎么写简历_写简历网站

你们在制作简历时,是不是基本只关注两件事:简历模板,还有基本信息的填写。 当你再次坐下来更新你的简历时,可能会发现自己不自觉地选择了那个“看起来最好看的模板”,填写基本信息,却没有深入思考如何使简历更具吸引力。这其实是一个普遍现象:许多求职者仍停留在传统简历…

【Unity3D小技巧】Unity3D中UI控制解决方案

推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址我的个人博客 大家好&#xff0c;我是佛系工程师☆恬静的小魔龙☆&#xff0c;不定时更新Unity开发技巧&#xff0c;觉得有用记得一键三连哦。 一、前言 在开发中总是会控制UI界面&#xff0c;如何优雅的控制UI界面是…

12.1 主成分分析原理(PCA)

主成分分析步骤如下&#xff1a; 设有条维数据 1. 将原始数据按列组成行列矩阵 &#xff1b; 2. 将矩阵 的每一行进行零均值化&#xff1b; 3. 求出协方差矩阵&#xff1b; 4. 求出协方差矩阵的特征值及对应的特征向量&#xff1b; 5. 将特征向量按对应特征值大小从上到…

富文本编辑器CKEditor4简单使用-04(跟源码设置image2插件图片的默认宽高等相关配置)

富文本编辑器CKEditor4简单使用-04&#xff08;跟源码设置image2插件图片的默认宽高等相关配置&#xff09; 1. 前言1.1 CKEditor4快速上手 2. CKEditor4的一般配置2.1 工具栏相关2.2 插件相关2.3 设置界面宽高等 3. CKEditor4的图片相关配置3.1 关于增强的图像插件的必要配置3…

洛谷 P3817 小A的糖果

题目描述 小 A 有 n 个糖果盒&#xff0c;第 i 个盒中有 a【i​】 颗糖果。 小 A 每次可以从其中一盒糖果中吃掉一颗&#xff0c;他想知道&#xff0c;要让任意两个相邻的盒子中糖的个数之和都不大于 x&#xff0c;至少得吃掉几颗糖。 输入格式 输入的第一行是两个用空格隔…

nop-entropy可逆计算入门(1)

第1步&#xff1a;从大佬的gitee&#xff1a;https://gitee.com/canonical-entropy/nop-entropy下载源码&#xff0c;进行本地编译&#xff0c;具体编译看项目下的readme,想偷懒的可以下载我编译后的jar&#xff0c;放到自己的maven仓库 https://pan.baidu.com/s/1p9MOh40MJ2m…

unity 拖入文件 窗口大小

目录 unity 拖入文件插件 设置窗口大小 unity 拖入文件插件 GitHub - Bunny83/UnityWindowsFileDrag-Drop: Adds file drag and drop support for Unity standalong builds on windows. 设置窗口大小 file build

【kubernetes】集群网络(二):Flannel的VxLan、Host-GW模式

文章目录 1 Pod的IP地址的分配2 CNI3 Flannel3.1 Flannel的安装3.2 VxLan3.3 Host-GW 4 总结 1 Pod的IP地址的分配 当节点上只安装了docker&#xff0c;则会用veth pairdocker0实现单个节点上容器之间的通信&#xff0c;并且这些容器都在同一个IP段&#xff0c;如果不修改&…

挑战杯 LSTM的预测算法 - 股票预测 天气预测 房价预测

0 简介 今天学长向大家介绍LSTM基础 基于LSTM的预测算法 - 股票预测 天气预测 房价预测 这是一个较为新颖的竞赛课题方向&#xff0c;学长非常推荐&#xff01; &#x1f9ff; 更多资料, 项目分享&#xff1a; https://gitee.com/dancheng-senior/postgraduate 1 基于 Ke…

STM32F407移植OpenHarmony笔记8

继上一篇笔记&#xff0c;成功开启了littlefs文件系统&#xff0c;能读写FLASH上的文件了。 今天继续研究网络功能&#xff0c;让控制台的ping命令能工作。 轻量级系统使用的是liteos_m内核lwip协议栈实现网络功能&#xff0c;需要进行配置开启lwip支持。 lwip的移植分为两部分…

8、应急响应-战前溯源反制主机蜜罐系统HFishHIDSElkeidWazuh

用途&#xff1a;个人学习笔记&#xff0c;欢迎指正 目录 背景&#xff1a; 一、潮源反制-平台部署-蜜罐-Hfish 二、溯源反制-平台部署-HIDS-Wazuh 三、溯源反制-平台部署-HlDS-Elkeid-hub 背景&#xff1a; 攻击者对服务器存在着各种威胁行为&#xff0c;作为安全人员&am…

SpringBoot security 安全认证(二)——登录拦截器

本节内容&#xff1a;实现登录拦截器&#xff0c;除了登录接口之外所有接口访问都要携带Token&#xff0c;并且对Token合法性进行验证&#xff0c;实现登录状态的保持。 核心内容&#xff1a; 1、要实现登录拦截器&#xff0c;从Request请求中获取token&#xff0c;从缓存中获…

【自然语言处理】P2 PyTorch 基础 - 张量

目录 安装 PyTorch张量创建张量操作张量索引、切片、联合操作 CUDA张量 本系列博文我们将使用 PyTorch 来实现深度学习模型等。PyTorch 是一个开源的、社区驱动的深度学习框架。拥有强大的工具和库生态系统&#xff0c;包含 TorchVision&#xff08;用于图像处理&#xff09;、…

2024 IC FPGA 岗位 校招面试记录

引言 各位看到这篇文章时&#xff0c;24届校招招聘已经渐进尾声了。 在这里记录一下自己所有面试&#xff08;除了时间过短或者没啥干货的一些研究所外&#xff0c;如中电55所&#xff08;南京&#xff09;&#xff0c;航天804所&#xff08;上海&#xff09;&#xff09;的经…

图像去噪——SpatiallyAdaptiveSSID网络推理测试(详细图文教程)

SpatiallyAdaptiveSSID 是一种有效的图像去噪方法&#xff0c;它通过自适应地处理不同区域的噪声&#xff0c;能够在保持图像细节的同时&#xff0c;有效地去除噪声。 目录 一、SpatiallyAdaptiveSSID网络简介二、源码包准备2.1 测试集2.2 模型权重文件 三、测试环境四、推理测…