数据结构——队列(C++实现)

数据结构——队列

  • 什么是队列
  • 顺序队列
  • 链式队列实现

今天我们接着来看队列

什么是队列

队列是一种基础且广泛应用的数据结构,它具有以下核心特征:

定义

  • 线性表:队列是一种特殊的线性表,其中的元素呈线性排列,每个元素都有一个直接前驱和一个直接后继(除了第一个元素无前驱,最后一个元素无后继)。

操作特性

  • 先进先出 (FIFO):队列遵循“先进先出”原则,即最早进入队列的元素将最先离开队列。这意味着新元素总是被添加到队列的末端(称为队尾),而删除或访问元素则发生在队列的另一端(称为队头)。

基本操作

  1. 入队(Enqueue):在队尾添加一个新元素。新元素成为队列中的最新成员,并且在任何已存在的元素之后等待被处理。
  2. 出队(Dequeue):从队头移除并返回最先进入队列的元素。这个操作确保了队列中最早加入的元素优先被处理。
  3. 查看队头(Peek 或 Front):返回队头元素但不移除它,允许查看即将被出队的元素而不改变队列状态。
  4. 判空(IsEmpty):检查队列是否为空,即没有任何元素。

实现方式

  • 顺序队列:使用固定大小的数组来存储元素。队头和队尾的索引需要进行适当的调整以确保正确的插入和删除。当队列满时,可能需要循环利用数组空间(环形缓冲区)或动态扩容。
  • 链式队列:使用链表结构来存储元素,队头和队尾分别对应链表的首节点和尾节点。链式队列通常更易于处理队列满和队列空的情况,因为节点的增删不受固定数组大小的限制。

应用
队列在计算机科学和许多实际应用场景中广泛使用,例如:

  • 任务调度:安排任务按照提交顺序依次执行。
  • 资源管理:如打印机任务队列,请求按照到达顺序打印文档。
  • 消息传递:在分布式系统中,消息队列用于异步通信,发送者将消息放入队列,接收者按照消息进入队列的顺序进行消费。
  • 缓存失效策略:如最近最少使用 (LRU) 缓存算法可以用双端队列来实现。
  • 广度优先搜索 (BFS):在图算法中,队列用于存储待探索的节点,确保按层级顺序遍历。

总结来说,队列是一个线性数据结构,它按照先进先出的原则组织和管理元素,支持在队尾进行插入(入队)和在队头进行删除(出队)的操作,常用于实现排队、任务调度、消息传递等场景,并可通过数组或链表等方式实现。

顺序队列

我们这里可以用一个数组来模拟队列(就是一个插入删除受限的顺序表):

在这里插入图片描述

#pragma once
#include<iostream>
#include<cassert>
#include<algorithm>

template<class T>
class MyQueue
{
public:
    // 默认构造函数,初始容量为 10
    MyQueue()
        : _capacity(10)
        , _size(0)
    {
        _data = new T[_capacity]; // 分配动态数组,大小为初始容量
    }

    // 自定义容量的构造函数
    MyQueue(const size_t capacity)
        : _capacity(capacity)
        , _size(0)
    {
        _data = new T[_capacity]; // 分配动态数组,大小为指定容量
    }

    // 判断是否需要扩容,并在必要时进行扩容
    T* resizeIfNecessary()
    {
        if (_size >= _capacity) // 当元素数量达到或超过当前容量时,需要扩容
        {
            size_t newcapceity = _capacity * 2; // 新容量为原容量的两倍

            T* tmp = new T[newcapceity]; // 分配新内存

            if (tmp == nullptr)
            {
                exit(EXIT_FAILURE); // 如果内存分配失败,程序终止
            }

            std::copy_n(_data, _size, tmp); // 将原有数据复制到新内存

            delete[] _data; // 释放旧内存
            _data = tmp; // 更新数据指针
            _capacity = newcapceity; // 更新容量

            std::cout<< "has resize!!" << std::endl; // 输出扩容提示信息
        }

        return _data; // 返回当前数据指针
    }

    // 在队尾添加一个元素
    void push(const T& data)
    {
        // 判断是否需要扩容并执行扩容操作
        resizeIfNecessary();

        _data[_size] = data; // 将新元素添加到队尾
        _size++; // 增加元素数量
    }

    // 移除并返回队头元素
    T pop()
    {
        assert(_size > 0); // 断言队列非空

        T first = _data[0]; // 保存队头元素

        for(int i = 0; i < (int)_size - 1; i++) // 通过元素移动而非元素拷贝的方式移除队头元素
        {
            _data[i] = _data[i+1];
        }

        _size--; // 减少元素数量

        return first; // 返回已移除的队头元素
    }

    // 返回队头元素(不移除)
    const T& front() const
    {
        assert(_size > 0); // 断言队列非空

        return _data[0]; // 返回队头元素
    }

    // 判断队列是否为空
    bool empty() const
    {
        return _size == 0; // 若元素数量为 0,则队列为空
    }

    // 打印队列中的所有元素
    void PrintQueue()
    {
        for(int i = 0; i < _size; i++) // 输出队列中的每个元素
        {
            std::cout << _data[i] << " ";
        }

        std::cout<<std::endl; // 输出换行
    }

    // 析构函数,释放动态数组
    ~MyQueue()
    {
        delete[] _data; // 释放动态数组内存
    }

private:
    T* _data; // 动态数组,用于存储队列元素

    size_t _capacity; // 队列当前容量

    size_t _size; // 队列当前元素数量
};

链式队列实现

在这里插入图片描述

我们这里用带头结点和带尾指针的单链表来实现:

#pragma once
#include<iostream>
#include<cassert>

// 结点类模板
// 用于存储链式队列中的数据元素和指向下一个结点的指针
template<class T>
struct Node
{
    // 默认构造函数,初始化数据域为默认构造的T对象,并将_next设为nullptr
    Node() : _next(nullptr), _data(T()) {}

    // 构造函数,接收一个T类型的参数data,用以初始化数据域,并将_next设为nullptr
    Node(const T& data) : _next(nullptr), _data(data) {}

    // 数据域,存储链式队列中实际的数据
    T _data;

    // 指向下一个结点的指针
    Node<T>* _next;
};

// 链式队列模板类
// 提供入队、出队、查看队首元素、判断队列是否为空等操作
template<class T>
class MyQueue
{
    // 内部类型别名,简化代码中的结点类型名称
    typedef Node<T> _Node;

public:
    // 构造函数,初始化队列为空,头尾指针均指向空结点
    MyQueue() : _size(0)
    {
        _head = new _Node();
        _tail = _head;
    }

    // 创建新结点,接收一个T类型的参数data,返回新建的结点指针
    _Node* createNode(const T& data)
    {
        _Node* newnode = new _Node(data);

        if (newnode == nullptr)
        {
            exit(EXIT_FAILURE); // 如果内存分配失败,程序退出
        }

        return newnode;
    }

    // 入队操作,将给定数据插入队尾
    void push(const T& data)
    {
        _Node* newnode = createNode(data);

        if (_head->_next == nullptr)
        {
            _head->_next = newnode;
            _tail = newnode;
        }
        else
        {
            _tail->_next = newnode;
            _tail = newnode;
        }
        _size++; // 入队成功后,队列大小增一
    }

    // 出队操作,移除并返回队首元素
    T pop()
    {
        assert(_size > 0); // 若队列为空,程序终止

        _Node* first = _head->_next;

        T first_data = first->_data; // 保存队首元素的值

        _Node* second = first->_next;

        _head->_next = second; // 更新头结点的_next,使其指向原队首元素的下一个结点

        delete first; // 删除原队首结点

        --_size; // 出队成功后,队列大小减一

        return first_data; // 返回原队首元素的值
    }

    // 查看队首元素(不移除)
    const T& front() const
    {
        assert(_size > 0); // 若队列为空,程序终止
        return _head->_next->_data; // 直接返回队首元素的值
    }

    // 判断队列是否为空
    bool empty() const
    {
        return _size == 0; // 如果队列大小为0,说明队列为空
    }

    // 打印队列中的所有元素
    void PrintQueue()
    {
        _Node* cur = _head->_next;

        while (cur)
        {
            std::cout << cur->_data << " "; // 输出当前结点的值
            cur = cur->_next; // 移动到下一个结点
        }

        std::cout << "END" << std::endl; // 打印结束标记
    }

private:
    // 头结点,其_next指向队首元素
    _Node* _head;

    // 尾结点,始终指向队尾元素
    _Node* _tail;

    // 队列中元素的数量
    size_t _size;
};

我们这里简单介绍一下,我们要看的几种队列的变形——双端队列,循环队列

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

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

相关文章

宝宝洗衣机买几公斤?四款精心挑选实用婴儿洗衣机推荐

家里有孩子的&#xff0c;条件允许的话&#xff0c;婴儿洗衣机还是非常有必要买的。由于宝宝的年纪还小&#xff0c;使得宝宝的皮肤比较娇嫩&#xff0c;与成人衣物分开洗护&#xff0c;可以为宝宝带来更加健康的生长环境&#xff0c;并且可以避免与大人衣物混洗所带来的细菌的…

【训练营】DateWhale——动手学大模型应用开发(更新中)

文章目录 写在前面大模型简介LLM简介RAG简介LangChain开发框架开发LLM应用的整体流程 写在前面 大模型时代从GPT爆发开始到现在已有一年多了&#xff0c;深度学习发展之快无法想象&#xff0c;一味感叹技术发展速度超越个人学习速度是没用的&#xff0c;倒不如花点时间参加一些…

其它IO合集

其它IO合集 1. 缓冲流1.1 概述1.2 字节缓冲流构造方法效率测试 1.3 字符缓冲流构造方法特有方法 2. 转换流2.1 字符编码和字符集字符编码字符集 2.2 编码引出的问题2.3 InputStreamReader类构造方法指定编码读取 2.4 OutputStreamWriter类构造方法指定编码写出转换流理解图解 3…

网络协议——IS-IS协议详解

1. IS-IS是什么 IS-IS是一种基于链路状态并使用最短路径优先算法进行路由计算的一种IGP协议。IS-IS属于内部网关协议&#xff0c;用于自治系统内部。IS-IS是一种链路状态协议&#xff0c;使用最短路径优先算法进行路由计算。 2. 应用场景&#xff08;园区网和骨干网&#xff0…

冯诺依曼与进程【Linux】

文章目录 冯诺依曼体系结构&#xff08;从硬件的角度描述&#xff09;冯诺依曼体系结构&#xff08;从软件的角度描述&#xff09;操作系统&#xff08;软件&#xff09;理解管理系统调用和库函数进程查看进程的两种方式 通过系统调用获取进程的PID和PPID通过系统调用创建进程-…

RAG学习笔记系列(一)

RAG 介绍 RAG 全称为 Retrieval Augmented Generation&#xff08;检索增强生成&#xff09;。是基于LLM构建系统的一种架构。 RAG 基本上可以理解为&#xff1a;搜索 LLM prompting。根据用户的查询语句&#xff0c;系统会先使用搜索算法获取到相关内容作为上下文&#xff0…

IMU应用于膝关节功能评估

近日&#xff0c;来自中国的研究团队开发了一款基于IMU的可穿戴系统&#xff0c;用于评估膝关节骨关节炎引发的功能障碍。研究着重重验证该系统在测量步态及下肢功能方面的准确性&#xff0c;通过对比业界公认的运动捕捉和步态分析系统&#xff0c;评估IMU传感器在这一领域的性…

Compose 简单组件

文章目录 Compose 简单组件TextText属性使用AnnotatedStringSpanStyleParagraphStyle SelectionContainer 和 DisableSelectionClickableText TextFieldTextField属性使用OutlinedTextFieldBasicTextFieldKeyboardOptions 键盘属性KeyboardActions IME动作 ButtonButton属性使用…

Python 数据结构和算法实用指南(三)

原文&#xff1a;zh.annas-archive.org/md5/66ae3d5970b9b38c5ad770b42fec806d 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 第七章&#xff1a;哈希和符号表 我们之前已经看过数组和列表&#xff0c;其中项目按顺序存储并通过索引号访问。索引号对计算机来说很有效…

Docker+Uwsgi+Nginx部署Django项目保姆式教程

之前&#xff0c;我和大家分享了在docker中使用uwsgi部署django项目的教程。这次&#xff0c;为大家带来的是使用DockerUwsgiNginx部署Django项目。废话不多说&#xff0c;我们开干。 步骤1&#xff1a;使用命令创建一个django项目 我这里python版本使用的是3.9.x 首先&#…

有爱有乐有知识,还有《米小圈上学记》!

“读万卷书&#xff0c;不如行万里路”&#xff0c;说的是读再多的书&#xff0c;也比不上走过万水千山所得。可是又有几人能得尝山水之妙&#xff0c;大多被困于尘世中。我虽走过一些山水&#xff0c;但大多因生存困于一隅&#xff0c;不得随心而行。 然而&#xff0c;读书也…

nmon进行性能资源监控

一、前言 在工作中可能会遇到需要在压测的时候对Linux服务器进行性能资源监控的情况。这时可以用nmon来对服务器进行监控。 二、nmon的下载安装 1.查看系统信息 cat /etc/os-release 结果为 PRETTY_NAME"Debian GNU/Linux 12 (bookworm)"NAME"Debian GNU/…

不用Linux也可以的强大文本处理方法

不用Linux也可以的强大文本处理方法 标题党了&#xff0c;其实是论VIM的使用。 做生物信息分析最合适的还是Linux操作系统&#xff0c;所以生信宝典在最开始就推出了Linux学习系列&#xff0c;由浅入深的讲述了Linux学习中的关键点。 主要文章列举如下&#xff1a; Linux学…

代码随想录算法训练营DAY25|C++回溯算法Part.2|216. 组合总和II、17.电话号码的字母组合

文章目录 216. 组合总和II题意理解树形结构伪代码实现剪枝操作CPP代码实现 17.电话号码的字母组合解题思路树形结构伪代码实现隐藏回溯CPP代码 216. 组合总和II 力扣题目链接 文章讲解&#xff1a;216. 组合总和III 视频讲解&#xff1a;和组合问题有啥区别&#xff1f;回溯算法…

python复制文件夹内容

参考博客 https://blog.csdn.net/itfans123/article/details/133710731 案例1 import os import shutildef copy_folder(source_folder, destination_folder):# 创建目标文件夹os.makedirs(destination_folder, exist_okTrue)# 遍历源文件夹中的所有文件和文件夹for item in …

【简单讲解下如何用爬虫玩转石墨文档】

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

力扣算法-回溯

递归 104.二叉树的最大深度 回溯 17.电话号码的字母组合 ①子集型回溯 78.子集 (1)选不选 (2)选哪个 131.分割回文串 &#xff08;1593.拆分字符串使唯一子字符串的数目最大 也可以用这个思路解&#xff1a;从结果角度&#xff0c;分割字符串&#xff09; ②组合型回溯…

【C++】哈希二

上篇博客我们写了解决哈希冲突的两种办法&#xff0c;不过我们写的都是针对整形的&#xff0c;而在实际情况下&#xff0c;要存入哈希表中的数据可以是string或自定义类型等等。那么我们就应该想一种办法去解决这里的问题。 比如说string&#xff0c;我们想到如何让string也转为…

代码随想录算法练习Day11:链表相交

题目&#xff1a;给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点&#xff0c;返回 null 。 题目链接&#xff1a;160.链表相交 题目思路&#xff1a;定义两个指针&#xff0c;分别遍历两链表&#xff0c;如…

后端获取请求体Body,将请求体进行解密放回Request请求,并能通过@RequestBody获取

目前系统发送的post和put请求都是没有加密数据。客户需要将请求体加密。而系统已经基本开发完成&#xff0c;不可能一个一个去修改发送的请求。就需要在发送请求时候在拦截器中将body进行加密。并且在后端进行请求过滤解密&#xff0c;并且能通过RequestBody继续获取对象。 1.…
最新文章