C++的数据结构(二)

news2024/12/23 20:31:37

一、链表的基本概念

        链表(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.coloradmin.cn/o/1664078.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

飞利浦|西圣开放式耳机怎么选?爆款机型深度对比!

现在&#xff0c;开放式耳机以其独特的非入耳设计&#xff0c;成为了市场上的新宠。开放式耳机在佩戴上不仅能让我们长时间享受舒适的体验&#xff0c;更告别了入耳式耳机会导致的耳闷头昏的烦恼。但是现在&#xff0c;面对市场上琳琅满目的品牌和型号&#xff0c;许多消费者感…

vue3属性透传(透传 Attributes),支持多个根节点,且可以在JavaScript 中访问透传 Attributes

支持多个根节点&#xff0c;且可以在JavaScript 中访问透传 Attributes Index.vue: <script setup> import { ref, onMounted } from vue import Child from ./Child.vue import ./index.cssconst handleClick () > {console.log(1) }onMounted(() > {}) </s…

静态IP设置:小白必看攻略

在现代网络环境中&#xff0c;IP地址是连接互联网的基石。它就像网络世界中的门牌号&#xff0c;让数据能够在庞大的网络海洋中准确找到目标。其中&#xff0c;静态IP地址由于其固定不变的特性&#xff0c;在某些特殊应用场景下尤为重要。那么&#xff0c;如何设置静态IP地址呢…

Ubuntu18.04设置SSH密钥登录

我们一般使用 VSCode 、MobaXterm、PuTTY等 SSH 客户端来远程管理 Linux 服务器。但是&#xff0c;一般的密码方式登录&#xff0c;容易有密码被暴力破解的问题。所以&#xff0c;一般我们会将 SSH 的端口设置为默认的 22 以外的端口&#xff0c;或者禁用 root 账户登录。但是即…

json-server 模拟接口服务

前端开发经常需要模拟接口请求&#xff0c;可以通过 json-server 实现。 1. 安装 json-server 在前端项目的终端命令行中执行 npm i json-server2. 创建数据源 在项目中新建文件 db.json &#xff0c;与 package.json 同级&#xff0c;内容为模拟的数据 注意 json 文件对格式…

40道 Nginx 高频面试题

Nginx 是一款轻量级的 Web 服务器、反向代理服务器&#xff0c;由于它的内存占用少&#xff0c;启动极快&#xff0c;高并发能力强&#xff0c;在互联网项目中广泛应用。那么关于 Nginx 的核心面试题有哪些呢&#xff1f;今天给大家整理了40道Nginx 高频面试题&#xff0c;最近…

Java 常见集合类

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

使用AudioCraft(MusicGen)生成音乐

AudioCraft 是一个 PyTorch 库,用于音频生成的深度学习研究。AudioCraft 包含两个先进的AI生成模型:AudioGen和MusicGen,它们共同致力于生成高质量的音频内容。 MusicGen是一个简单且可控的音乐生成模型。它利用Meta提供的20K小时授权音乐进行训练,能够根据文本描述或已有…

1_1. Linux简介

1_1. Linux简介 文章目录 1_1. Linux简介1. 我们用linux来干嘛2. 计算机组成3. 操作系统4. Linux哲学思想5. Linux目录6. Linux分区类型 1. 我们用linux来干嘛 1. 大家都知道linux是一个操作系统&#xff0c;它是一个基础的软件&#xff0c;操作系统是硬件与应用程序的中间层。…

JS中的arguments是什么?

arguments是当我们不确定有多少个参数传递时&#xff0c;就可以使用argument来获取。在js中&#xff0c;arguments实际上就是当前函数的一个内置对象&#xff0c;存储了我们传递的所有实参。arguents的展示形式就是一个伪数组&#xff0c;所以我们可以对它进行遍历。 我们先来…

使用J-Link Commander / JFlash 烧写固件程序(以STM32F103C8T6为例)

使用JFlash 烧写流程 运行JFlash, 点击Project Settings 配置Jlink为SWD方式,选择连接设备为STM32F103C8T6, 点击确定. 选择要烧录的Bin文件 设置bin文件烧录地址, 点击OK(地址要在0x08000000-0x0800FFFF范围内) Note : STM32F103C8T6 Flash大小为 64KB&#xff0c; 地址范围…

Windows只能安装在GPT磁盘上

转换磁盘分区形式 步骤1. 先按照正常流程使用Windows系统安装光盘或系统U盘引导计算机。 步骤2. 在Windows安装程序中点击“开始安装”&#xff0c;然后按ShiftF10打开命令提示符。 步骤3. 依次输入以下命令&#xff0c;并在每一行命令后按一次Enter键执行。 步骤4. 等待转换…

[Linux][网络][数据链路层][一][以太网][局域网原理]详细讲解

目录 0.对比理解"数据链路层"和网络层1.以太网1.认识以太网2.以太网帧格式3.认识MAC地址4.以太网帧格式如何封装/解包&#xff1f;5.以太网帧格式如何分用&#xff1f; 2.重谈局域网通信原理0.如何形象的理解&#xff1f;1.理解局域网通信2.在发送数据的时候&#xf…

连接和使用vCenter Server嵌入式vPostgres数据库

vCenter Server 早期支持内嵌(embedded)和外部(external)数据库,内嵌数据库就是vPostgres,基于VMware Postgres数据库(PostgreSQL数据库),外部数据库用的多的是Oracle数据库和SQL Server数据库。因为早期使用内嵌的PostgreSQL数据库只能用于小型环境,比如仅支持几十台…

算法详解——回溯法

一、回溯法概述——问题背景 回溯法是一种解决约束满足问题的方法&#xff0c;特别适用于解决组合问题、搜索优化问题等。它通过逐步构建候选解决方案并且在这个解决方案不再可能满足约束或条件时进行剪枝和回溯。具体来说&#xff0c;回溯法可以应用于以下类型的问题&#xff…

【高阶数据结构】图--最短路径问题

图--最短路径问题 一、单源最短路径--Dijkstra算法1、简介2、解析3、代码4、测试用例5、打印最小路径代码和测试6、缺陷&#xff1a;不能使用负路径 二、单源最短路径--Bellman-Ford算法1、简介2、解析&#xff08;1&#xff09;详情i、负权问题&#xff1a;一个点只跑一趟找最…

整除C++

题目&#xff1a; 思路&#xff1a; 循环遍历7(可以被 7 整除的正整数最小为7)~n&#xff0c;如果i%70&#xff0c;就说明i可以被7整除. 代码&#xff1a; #include<iostream> using namespace std; int n;//一个正整数 int main(){scanf("%d",&n);//输入…

Vue.js【路由】

初识路由 提到路由&#xff08;Route&#xff09;&#xff0c;一般我们会联想到网络中常见的路由器&#xff08;Router&#xff09;&#xff0c;那么路由和路由器之间有什么关联呢&#xff1f;路由是指路由器从一个接口接收到数据&#xff0c;根据数据的目的地址将数据定向传送…

git-删除workspace.xml的跟踪

问题描述 .gitignore 文件内容如下&#xff1a; .pyc *.pyc user_files/ .vscode/ __pycache__//.idea/misc.xml /.idea/modules.xml /.idea/inspectionProfiles/profiles_settings.xml /.idea/inspectionProfiles/Project_Default.xml /.idea/batrp_webbackend-server-dev.i…

Java 开发 框架安全:Spring 漏洞序列.(CVE-2022-22965)

什么叫 Spring 框架. Spring 框架是一个用于构建企业级应用程序的开源框架。它提供了一种全面的编程和配置模型&#xff0c;可以简化应用程序的开发过程。Spring 框架的核心特性包括依赖注入&#xff08;Dependency Injection&#xff09;、面向切面编程&#xff08;Aspect-Or…