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`对象,然后向其中添加了几首歌曲。我们打印了播放列表的大小,并播放了整个播放列表。之后,我们删除了一个歌曲,并再次打印了播放列表的大小。最后,我们清除了播放列表,并检查它是否为空。

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

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

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

相关文章

(二刷)代码随想录第4天|24. 两两交换链表中的节点 ● 19.删除链表的倒数第N个节点

24. 两两交换链表中的节点 24. 两两交换链表中的节点 - 力扣&#xff08;LeetCode&#xff09; 代码随想录 (programmercarl.com) 帮你把链表细节学清楚&#xff01; | LeetCode&#xff1a;24. 两两交换链表中的节点_哔哩哔哩_bilibili 给你一个链表&#xff0c;两两交换其…

【计算机网络】物理层传输介质 习题3

双绞线是用两根绝缘导线绞合而成的&#xff0c;绞合的目的是( )。 A.减少干扰 B.提高传输速度 C.增大传输距离 D.增大抗拉强度 在电缆中采用屏蔽技术带来的好处主要是( ) A.减少信号衰减 B. 减少电磁干扰辐射 C.减少物理损坏 D. 减少电缆的阻抗 利用一根同轴电缆互连主机构成…

docker安装时报错:Error: Nothing to do

安装docker时报以下错误 解决方法&#xff1a; 1.下载关于docker的相关依赖环境 yum -y install yum-utils device-mapper-persistent-data lvm22.设置下载Docker的镜像源 yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo3…

Java 常见集合类

集合的整体框架 Java 的集合&#xff0c;也可以叫做容器&#xff0c;根据集合的整体框架可以看出&#xff0c;主要是两大集合接口&#xff1a;第一个是 Collection 接口&#xff0c;主要用来存放单一的元素对象&#xff1b;另一个是 Map 接口&#xff0c;主要用于存储键值对。…

Linux·基本指令

从本节开始将新开一个关于Linux操作系统的板块&#xff0c;其实Linux也没什么太神秘的&#xff0c;就是一个操作系统(OS)嘛&#xff0c;跟Windows操作系统是一个概念&#xff0c;只不过Windows中的大部分操作都是用光标点击来进行人机交互&#xff0c;但是Linux是通过输入命令行…

SQLite性能测试(插入)

最近一直在思考一个问题&#xff0c;SQLite 做到这么轻量级&#xff0c;那它注定不会像 MySql 一样强性能&#xff0c;那么它的性能怎么样呢&#xff1f;并发量多高呢&#xff1f; 官方解释&#xff1a; About SQLite 最大数据库大小&#xff1a;281TB 最大行大小&#xff1…

俄罗斯方块的代码实现

文章目录 首先是头文件的引入部分接下来是一些预处理指令接下来定义了两个结构体&#xff1a;接下来是全局变量g_hConsoleOutput&#xff0c;用于存储控制台输出句柄。之后是一系列函数的声明最后是main函数源码 首先是头文件的引入部分 包括stdio.h、string.h、stdlib.h、tim…

Neo4j 之安装和 CQL 基本命令学习

正常使用结构化的查询语言 SQL&#xff08;Structured Query Language&#xff09;较多一些&#xff0c;但是像 Neo4j 这种非结构化的图形数据库来说&#xff0c;就不得不学习下 CQL&#xff08;Cypher Query Language&#xff09;语言了。如果你之前学过 《离散数学》或《图论…

【北京迅为】《iTOP-3588从零搭建ubuntu环境手册》-第6章 安装Samba

RK3588是一款低功耗、高性能的处理器&#xff0c;适用于基于arm的PC和Edge计算设备、个人移动互联网设备等数字多媒体应用&#xff0c;RK3588支持8K视频编解码&#xff0c;内置GPU可以完全兼容OpenGLES 1.1、2.0和3.2。RK3588引入了新一代完全基于硬件的最大4800万像素ISP&…

Debian安装Redis、RabbitMQ、Nacos

安装Redis&#xff1a; 启动Redis、开机自启动 sudo systemctl start redis-server #启动sudo systemctl enable redis-server #开机自启 Redis状态(是否在运行) sudo systemctl status redis-server #查看运行状态 redis-cli ping # 客户端尝试连接 安装RabbitMQ&#xff0c;…

Rust学习笔记(中)

前言 笔记的内容主要参考与《Rust 程序设计语言》&#xff0c;一些也参考了《通过例子学 Rust》和《Rust语言圣经》。 Rust学习笔记分为上中下&#xff0c;其它两个地址在Rust学习笔记&#xff08;上&#xff09;和Rust学习笔记&#xff08;下&#xff09;。 错误处理 pani…

百问C语言第1问——彻底弄懂define用法

系列文章目录 玩转指针专栏 趣味c程序专栏 一.c语言关系操作符练习题(新手必会) 一.c语言常见概念(超全) 一.趣味c程序—关机程序&#xff08;整蛊同学版) 二.趣味c程序—猜数字游戏&#xff08;含干货知识点 三.趣味c程序—打印图形&#xff08;1&#xff09;&#xff08;含干…

前端笔记-day05

文章目录 01-结构伪类选择器02-结构伪类选择器-公式用法03-伪元素选择器04-盒子模型-组成05-盒子模型-边框线06-盒子模型-单方向边框线07-盒子模型-内边距08-盒子模型-padding多值写法09-盒子模型-尺寸计算10-盒子模型-版心居中11-清除默认样式12-元素溢出overflow13-外边距合并…

Java | Leetcode Java题解之第83题删除排序链表中的重复元素

题目&#xff1a; 题解&#xff1a; class Solution {public ListNode deleteDuplicates(ListNode head) {if (head null) {return head;}ListNode cur head;while (cur.next ! null) {if (cur.val cur.next.val) {cur.next cur.next.next;} else {cur cur.next;}}return…

E - Yet Another Sigma Problem(ABC字典树)

思路&#xff1a;我们可以发现两个字符串的最长公共前缀就是字典树中的最近公共祖先。然而这道题&#xff0c;比如说某个结点是x个字符串的前缀&#xff0c;那么当前结点对答案的贡献为x * (x - 1) / 2&#xff0c;就是x中任选两个字符串组合&#xff0c;因为在这之前&#xff…

Linux提权--本地环境变量文件配合 SUID

免责声明:本文仅做技术交流与学习... 目录 背景: 前提条件: 演示: 实战中如何操作? 探针发现: 背景: 环境变量提权--------> 背景&#xff1a; 管理员编译了程序&#xff0c;给予了程序管理员运行的方案, 攻击通过对程序的运行调试反编译等得到了程序的运行大概逻辑, …

软考-软件工程

软件工程概述 软件工程指的是应用计算机科学、数学及管理科学等原理&#xff0c;以工程化的原则和方法来解决软件 问题的工程&#xff0c;目的是提高软件生产率、提高软件质量、降低软件成本。 概述&#xff1a; 软件开发模型&#xff1a;指导软件开发的体系 需求分析确定软件…

使用Remix部署智能合约到币安链(Remix的操作介绍 币安链合约的部署) 点赞收藏哦

大家好&#xff0c;我是程序员大猩猩呀。 据我所知&#xff0c;很多人进入币圈之后&#xff0c;想要通过炒币一夜暴富&#xff01;另一部分人呢他们希望自己能创建一个项目&#xff0c;然后发行自己的数字货币然后暴富。 不管是什么方式吧&#xff0c;只要不违法&#xff0c;…

原创未发表!24年新算法SBOA优化TVFEMD实现分解+四种熵值+频谱图+参数变化图+相关系数图!

声明&#xff1a;文章是从本人公众号中复制而来&#xff0c;因此&#xff0c;想最新最快了解各类智能优化算法及其改进的朋友&#xff0c;可关注我的公众号&#xff1a;强盛机器学习&#xff0c;不定期会有很多免费代码分享~ 目录 数据介绍 优化流程 创新点 使用TVFEMD的创…

【详细介绍下Visual Studio】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…