C++的数据结构(二)

一、链表的基本概念

        链表(Linked List)是一种物理存储单元上非连续的、非顺序的线性数据结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列节点(链表中每一个元素称为节点)组成,节点可以在运行时动态生成。每个节点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个节点地址的指针域。相比于线性数组,链表的好处在于不需要事先分配固定大小的存储空间,并且在插入和删除数据时不需要移动其他元素。

        最适合比喻链表的就是火车了,如下图所示。

二、链表的类型

        C++中常见的链表类型包括单向链表、双向链表和循环链表。

        1. 单向链表(Singly Linked List)

                单向链表是最简单的链表结构,每个节点只包含一个指向下一个节点的指针。单向链表只能从头节点开始顺序访问到尾节点。

                示例:在链表头部插入节点,遍历链表并打印节点的数据,代码如下。

#include <iostream>
// 定义单向链表的节点结构体
struct Node {int data;           // 数据域Node* next;        // 指针域,指向下一个节点// 构造函数,用于初始化节点Node(int data) : data(data), next(nullptr) {}
};
// 单向链表类
class SinglyLinkedList {
private:Node* head; // 头节点指针public:SinglyLinkedList() : head(nullptr) {}// 在链表头部插入节点void insertAtBeginning(int data) {Node* newNode = new Node(data);newNode->next = head;head = newNode;}// 遍历链表并打印节点的数据void printList() {Node* temp = head;while (temp != nullptr) {std::cout << temp->data << " ";temp = temp->next;}std::cout << std::endl;}// 销毁链表~SinglyLinkedList() {Node* temp;while (head != nullptr) {temp = head;head = head->next;delete temp;}}
};
int main() {SinglyLinkedList list;list.insertAtBeginning(5);list.insertAtBeginning(3);list.insertAtBeginning(1);list.printList();return 0;
}

                结果如下所示:

        2. 双向链表(Doubly Linked List)

                双向链表包含两个指针,一个指向前一个节点,另一个指向下一个节点。这使得双向链表可以从任何一个节点向前或向后遍历。

                 示例:在链表头部插入节点,遍历链表并打印节点的数据,代码如下。

#include <iostream>
// 定义双向链表的节点结构体
struct Node {int data;           // 数据域Node* prev;         // 指针域,指向前一个节点Node* next;         // 指针域,指向下一个节点// 构造函数,用于初始化节点Node(int data) : data(data), prev(nullptr), next(nullptr) {}
};
// 双向链表类
class DoublyLinkedList {
private:Node* head; // 头节点指针
public:DoublyLinkedList() : head(nullptr) {}// 在链表头部插入节点void insertAtBeginning(int data) {Node* newNode = new Node(data);if (head != nullptr) {head->prev = newNode;}newNode->next = head;head = newNode;}// 遍历链表并打印节点的数据void printList() {Node* temp = head;while (temp != nullptr) {std::cout << temp->data << " ";temp = temp->next;}std::cout << std::endl;}// 销毁链表~DoublyLinkedList() {Node* temp;while (head != nullptr) {temp = head;head = head->next;if (temp->next != nullptr) {temp->next->prev = temp->prev;}delete temp;}}
};
int main() {DoublyLinkedList list;list.insertAtBeginning(5);list.insertAtBeginning(3);list.insertAtBeginning(1);list.printList(); return 0;
}

                在上述2个示例中,我们定义了节点结构体,并为单向链表和双向链表创建了类。这些类包含了插入节点到链表头部、遍历链表并打印节点数据以及销毁链表的方法。对于双向链表,我们还需要处理`prev`指针以确保链表结构的正确性。

                请注意,以上代码只是一个简单的示例,并没有处理错误情况和异常。在实际应用中,你可能需要添加额外的错误检查和边界情况处理,例如当插入节点失败时返回错误码或抛出异常。此外,对于大型数据集,你可能还需要考虑性能优化和内存管理。

        3. 循环链表(Circular Linked List)

                循环链表与单向链表和双向链表的主要区别在于,循环链表的尾节点指向头节点,形成一个环状结构。这使得循环链表可以从任何节点开始遍历整个链表。循环链表是链表结构的一个变种,它的特点是链表的最后一个节点指向链表的第一个节点,形成一个环状结构。在实际开发中,循环链表常用于实现某些特殊的数据结构,如循环队列、循环双端队列等。

                我们举一个实例:假设我们正在开发一个音乐播放器应用,需要实现一个音乐播放列表,这个播放列表支持添加、删除、循环播放音乐的功能。循环链表非常适合用来实现这样的播放列表。

                首先,我们定义一个循环链表的节点类,每个节点包含一个音乐信息和一个指向下一个节点的指针。代码如下。

#include <iostream>
#include <string>
class MusicNode {
public:std::string title;    // 音乐标题MusicNode* next;       // 指向下一个节点的指针MusicNode(const std::string& title) : title(title), next(nullptr) {}
};

                接着,我们定义一个循环链表类,包含插入、删除、遍历等基本操作。代码如下。

class CircularMusicPlaylist {
private:MusicNode* head;       // 头节点int size;               // 链表大小public:CircularMusicPlaylist() : head(nullptr), size(0) {}~CircularMusicPlaylist() {clear();}// 插入音乐到播放列表void addMusic(const std::string& title) {MusicNode* newNode = new MusicNode(title);if (isEmpty()) {head = newNode;newNode->next = head; // 构成循环} else {MusicNode* current = head;while (current->next != head) {current = current->next;}current->next = newNode;newNode->next = head; // 新节点指向头节点,形成闭环}size++;}// 从播放列表中删除音乐void removeMusic(const std::string& title) {if (isEmpty()) {return;}MusicNode* prev = nullptr;MusicNode* current = head;do {if (current->title == title) {if (prev == nullptr) { // 删除的是头节点while (current->next != head) {current = current->next;}head = head->next;delete current;head->next = head; // 更新头节点的指针} else {prev->next = current->next;if (current == head) {head = prev->next;}delete current;}size--;return;}prev = current;current = current->next;} while (current != head); // 遍历整个链表std::cout << "Music not found in the playlist." << std::endl;}// 遍历播放列表void play() {if (isEmpty()) {std::cout << "Playlist is empty." << std::endl;return;}MusicNode* current = head;do {std::cout << "Playing: " << current->title << std::endl;current = current->next;} while (current != head); // 循环播放}// 判断播放列表是否为空bool isEmpty() const {return head == nullptr;}// 清除播放列表void clear() {MusicNode* current = head;while (current != nullptr) {MusicNode* toDelete = current;current = current->next;delete toDelete;}head = nullptr;size = 0;}// 获取播放列表大小int getSize() const {return size;}
};

                现在,我们可以使用这个循环链表播放列表了。代码如下。

int main() {CircularMusicPlaylist playlist;// 添加音乐到播放列表playlist.addMusic("Song 1");playlist.addMusic("Song 2");playlist.addMusic("Song 3");// 打印播放列表大小std::cout << "Playlist size: " << playlist.getSize() << std::endl;// 播放整个播放列表playlist.play();// 删除一个歌曲playlist.removeMusic("Song 2");// 再次打印播放列表大小std::cout << "Playlist size after removal: " << playlist.getSize() << std::endl;// 再次播放整个播放列表playlist.play();// 清除播放列表playlist.clear();// 检查播放列表是否为空if (playlist.isEmpty()) {std::cout << "Playlist is now empty." << std::endl;}return 0;
}

                上述代码中,我们首先创建了一个`CircularMusicPlaylist`对象,然后向其中添加了几首歌曲。我们打印了播放列表的大小,并播放了整个播放列表。之后,我们删除了一个歌曲,并再次打印了播放列表的大小。最后,我们清除了播放列表,并检查它是否为空。

                通过实现一个音乐播放列表,我们展示了循环链表的基本操作,包括添加元素、删除元素、遍历元素以及检查链表是否为空。在实际应用中,循环链表还可以用于实现各种需要循环访问数据结构的场景,如循环队列、环形缓冲区等。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网站实现微信扫码登录(利用微信开放平台实现)

第一步&#xff1a;微信开放平台账户申请 网址&#xff1a;微信开放平台 1.首先我们要做的就是进入到微信开放平台申请一个开放平台账户&#xff0c;获得资质&#xff01; &#xff1a;注册需要准备营业执照、1-2个工作日审批、300元认证费 &#xff1a;注册之后&#xff0…

常用Linux命令详细总结

一、文档编辑、过滤、查看命令 1、cp 复制文件和目录 -a 复制文件并保持文件属性 -d 若源文件为链接文件&#xff0c;则复制链接文件属性而非文件本身 -i 覆盖文件前提示&#xff0c;如果不要提示&#xff0c;在命令前加上\ -r 递归复制&#xff0c;通常用于目录的复制 …

python代码实现xmind思维导图转换为excel功能

目录 转换前xmind示例 运行代码转换后excel示例 python代码 转换前xmind示例 运行代码转换后excel示例 如果想要合并单元格内容&#xff0c;在后面一列参考输入 B2&C2&D2&E2 python代码 from xmindparser import xmind_to_dict import pandas as pd from openp…

初识多线程

1. 前置知识——进程 在学习多线程前需要了解操作系统中的基本知识&#xff0c;这里简单回顾下。 1.1 进程控制块 一个进程对应着一个进程控制块PCB&#xff0c;PCB是一个用于管理和维护进程信息的数据结构&#xff0c;这个数据结构中大致包含下面内容&#xff08;并不完整&…

Centos 中如何汉化man命令

刚学Linux&#xff0c;记不住命令和选项&#xff0c;很依赖里面的 man 查看命令&#xff0c;但因为着实看不懂&#xff0c;有没有什么办法把man查看命令的信息改成中文 在CentOS 7中&#xff0c;你可以通过安装man-pages-zh包来获取中文的man手册。以下是具体的步骤&#xff1a…

学生党性价比蓝牙耳机哪款好用?五款性价比机型盘点分享

在众多的蓝牙耳机里&#xff0c;对于许多预算不到的学生党来说&#xff0c;想要在有限的预算内挑选到一款性价比高、性能出色的蓝牙耳机&#xff0c;确实是一个不小的挑战&#xff0c;作为蓝牙耳机大户的我今天就来为大家盘点五款性价比极高的蓝牙耳机&#xff0c;帮助大家在有…

Sqlite在Mybatis Plus中关于时间字段的处理

我的个人项目中&#xff0c;使用Mybatis-Plus 和 Sqlite数据库&#xff0c; 但是在存储和查询时间字段的时候&#xff0c;总是出现问题&#xff0c;记录下我解决问题的过程。 Sqlite会默认把时间字段转成时间戳存储到数据库的字段中&#xff0c;看起来不直观&#xff0c;所以我…

永倍达 最新消息!发放消费券! 重新开网?

大家好 我是一家软件开发公司的产品经理 吴军 我又又又又又叕来蹭热度了&#xff0c;最近永倍达有新动作&#xff01;发放消费券&#xff1f; 店长群最新通知 4.15号开始发放消费券 一个月之后才可以重新提现 今天是5.10号离5.18也不远了 大家拭目以待看看到底能不能提现&a…

品鉴中的个人风格:如何形成自己与众不同的红酒品鉴体验

品鉴云仓酒庄雷盛红酒不仅是一种感官体验&#xff0c;更是一种个人风格的展现。每个人都有自己与众不同的品味和偏好&#xff0c;通过品鉴红酒&#xff0c;我们可以形成自己与众不同的红酒品鉴体验。 要形成自己与众不同的红酒品鉴体验&#xff0c;首先需要勇于尝试不同类型的红…

记一次favicon.ico的折腾

某项目需要将前端和后台整合在一起 我也不知道为啥要整合 上面有要求就整呗 正常前端npm run build打包后 dist内会根据设置自动生成favicon.ico文件在根目录下 但由于前后端整合 需要打包后将图标放在dist下的static文件夹里 需要的效果 打包后 index.html里 <link rel&…

LVS的三种工作模式---(DR/TUN/NAT)

目录 一、NAT模式&#xff08;LVS-NAT&#xff09; 二、IP隧道模式&#xff08;LVS-TUN&#xff09; 三、DR模型--直接路由模式&#xff08;LVS-DR&#xff09; LVS/DR模式ARP抑制 原因&#xff1a; LVS的DR工作模式及配置&#xff1a; LVS的NAT工作模式及配置&#xff1…

基于springboot实现贸易行业crm系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现贸易行业crm系统演示 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了基于springboot的贸易行业crm系统的开发全过程。通过分析基于springboot的贸易行业crm系统管理的不足&#xff0c;创建…