使用C++手写队列 - Queue

使用C++手写队列

  • 我们使用C++写过了Vector、Stack,这篇文章我们来写一下队列Queue;
  • Queue的特点是先进先出,或者说满足这一特性的我们可以称之为队列;
  • 和栈Stack一样,Queue只是定义了一组操作,满足一系列操作的就可以称为队列,此次我们采用顺序表进行实现,即对C++的动态数组进行简单的封装并且提供一系列队列应有的操作;
  • 在我们实现Stack的时候,我们利用了我们写的Vector,那么为什么这次不继续使用而是从最基本的动态数组入手呢。这是因为Queue的特性是先进先出,即队尾进,队头出。若我们使用Vector实现,直观的实现就是利用push_back入队,利用removeAt出队,入队还好,出队则会造成大量的元素移动,即造成时间浪费,这是顺序表的缺点。
  • 我们为了使用顺序表就不能采用传统的直接删除元素,这里我们采用一种循环利用的方式,出队后,元素并不移动,空出的位置就作为接下来入队元素的备用空间,这样一来使用Vector显得碍手碍脚,所以直接操作动态数组,这里的循环利用的核心操作就是取余运算,即能省时间又避免了出队后空出的空间不再被利用造成的空间的浪费。

采用这种存储结构,我们来构思各种操作的实现。
基本操作:

  • 我们利用两个索引(下标)来标明队列的头尾,begin标明对头的元素,end标明队尾元素的后一个,特别的,当队空时,begin = end。
    空队

  • 入队:即令end处的存放入队的元素,而后使end后移,这里需要注意的是,如果end到达数组的最后一处,则“后移”实际是将end移到数组头部,这样下次入队的时候就能利用数组前面由于出队空出的空间,这样的操作可以统一成end = (end + 1) % L(L为数组长度)
    入队

  • 出队:即释放begin处的元素,而后使begin后移即可,类似的,若begin已到数组尾,将其更新到数组头,即begin = (begin + 1) % L。
    出队

  • 取队头元素:begin处的元素即对头元素

扩展操作:

  • 判断队空:当begin和end相等时对空
  • 队列中的元素个数:
    • 当begin<end时,队大小等于end - begin
    • 当begin>end时,队大小等于L - (begin - end) = end - begin + L
    • 当begin=end时,队大小为0 = end - begin
    • 观察规律,队大小可统一为(end - begin) % L

基于以上的构思,编程实现

  • 首先,定义类结构和声明基本操作对应函数
    • 动态数组:_data
    • 队头索引:_begin
    • 队尾索引:_end
    • 数组大小:_capacity
    • 入队:push
    • 出队:pop
    • 队头:front
    • 对大小:size
    • 队空:empty
template<typename _Tp>  // 模板类,_Tp代表队列中元素的类型
class Queue {
    enum { DEFAULT_SIZE = 10 };  // 默认创建的数组大小
    typedef _Tp DataType;  // typdef
public:

    Queue();  // 构造函数
    
    ~Queue();  // 析构函数,释放动态创建的数组
    
    void push(const DataType & e);  // 入队
    
    void pop();  // 出队
    
    const DataType & front() const;  // 队头
    
    size_t size() const;  // 队大小
    
    bool empty() const;  // 对空
    
private:
    DataType * _data = NULL;  // 动态数组指针
    int _begin = 0;  // begin
    int _end = 0;  // end
    size_t _capacity = (size_t)DEFAULT_SZIE;  // 数组大小
};
  • 按照分析以此对一系列函数进行实现
// 以下代码均在类定义内

Queue() {

    // 创建动态数组
    _data = new DataType[_capacity];
}

~Queue() {

    // 释放动态创建的数组
    if (_data) delete [] _data;
}
void push(const DataType & e) {

    _data[_end] = e;  // 将end位置置为e
    _end = (_end + 1) % _capacity;  // 循环后移
}
void pop() {

    if (!empty()) {  // 不空才出队
    
        _data[_begin].~DataType();  // 释放begin处的元素
        _begin = (_begin + 1) % _capacity;  // 循环后移
    }
}
const DataType & front() const {

    return _data[_begin];  // 取begin处的元素,即队头
}
size_t size() const {

    return (_end - _begin + _capacity) % _capacity;  // 队列大小
}
bool empty() const {

    return _begin == _end;  // 判空
}

这样我们就实现了队列,但是此时的队列能容纳的最大元素个数时固定的,对于不能预判该使用多大的数组时不方便。所以,接下来,我们将实现当队满时进行动态扩容。


升级队列,动态扩容:

  • 什么时候队满?

    • 我们观察下图,当队将满时满足的条件是,size == L - 1, 注意不是L,如果在size == L时,在扩容,无法分别到底这是队满还是队空
      队满
  • 如何扩容?在实现我们的Vecror时,我们扩容采用的是二倍扩容,即扩容后的空间大小是原来的两倍,这里同样采用此策路。
    扩容

  • 代码实现

//  增加private函数extend负责扩容
void extend() {
        size_t siz = size();
        //  队满时才扩容
        if (siz == _capacity - 1) {
        
            // 开辟新空间
            DataType * tmp = new DataType[_capacity << 1];
            
            // 复制元素
            for (int i = _begin, j = 0; i != _end; i = (i + 1) % _capacity, ++j) {
                tmp[j] = _data[i];
            }
            
            // 更新begin,end,data指针,容量capacity
            _begin = 0;
            _end = siz;
            delete [] _data;  // 释放原有空间
            _data = tmp;
            _capacity <<= 1;
        }
    }

// 在push操作中添加扩容语句
void push(const DataType & e) {
        
        extend();  //  扩容
        
        _data[_end] = e;
        _end = (_end + 1) % _capacity;
}

简单测试:

  • 进行简单测试
#include <iostream>
#include "Queue.h"

// 测试宏
#define TEST_FOR_QUEUE(q) { \
    std::cout << "TEST at " << __LINE__ << " :\n"; \
    std::cout << "\tThe q is:"; print(std::cout, q); \
    std::cout << "\tq.empty() : " << q.empty() << '\n'; \
    std::cout << "\tq.size() : " << q.size() << '\n'; \
    std::cout << "\tq.front() : " << q.front() << '\n'; \
}

int main () {
    Queue<double> q;
    TEST_FOR_QUEUE(q);  // 输出相关信息
    
    for (int i = 1; i < 12; ++i) {  // 入队
        q.push(1 / (double)i);
    }
    TEST_FOR_QUEUE(q);  // 输出相关信息
    
    for (int i = 0; i < 5; ++i) {  // 出队
        q.pop();
    }
    TEST_FOR_QUEUE(q);  // 输出相关信息
    
    return 0;
}
  • 输出结果

Test

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
think-queue是一个基于ThinkPHP5框架的消息队列扩展,可以将耗时的任务异步处理,提高系统的并发能力和稳定性。以下是think-queue的介绍和演示: 介绍: 1. think-queue的安装:可以通过composer安装,具体命令为:composer require topthink/think-queue。 2. think-queue的配置:需要在config/queue.php文件中进行配置,包括连接信息、队列名称、超时时间等。 3. think-queue使用:可以通过php think queue:listen命令启动队列监听器,也可以通过php think queue:work命令启动队列处理器。 演示: 假设我们需要发送邮件,但是由于邮件发送需要连接SMTP服务器,因此会比较耗时。我们可以将邮件发送任务放入消息队列中异步处理,以提高系统的并发能力和稳定性。以下是一个简单的think-queue邮件发送示例: 1.定义邮件发送任务类: ```php namespace app\queue\job; use think\queue\Job; use PHPMailer\PHPMailer\PHPMailer; class SendMail { public function fire(Job $job, $data) { $mail = new PHPMailer(); // 邮件发送代码 if ($mail->send()) { // 邮件发送成功,删除任务 $job->delete(); } else { // 邮件发送失败,重新放入队列 $job->release(60); } } } ``` 2.将邮件发送任务加入消息队列: ```php use think\Queue; use app\queue\job\SendMail; // 将邮件发送任务加入消息队列 Queue::push(new SendMail($data)); ``` 3.启动队列监听器: ```shell php think queue:listen ``` 以上示例中,我们定义了一个SendMail类作为邮件发送任务,将其加入消息队列中异步处理。在fire方法中,我们使用PHPMailer类发送邮件,如果发送成功则删除任务,否则重新放入队列。最后,我们通过php think queue:listen命令启动队列监听器,等待任务的到来。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值