使用C++手写队列 - Queue

Posted 魔蛇学吧

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用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为数组长度)使用C++手写队列 - Queue

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

  • 取队头元素: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时,在扩容,无法分别到底这是队满还是队空 使用C++手写队列 - Queue
  • 如何扩容?在实现我们的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__ << " : "
    std::cout << " The q is:"; print(std::cout, q); 
    std::cout << " q.empty() : " << q.empty() << ' '
    std::cout << " q.size() : " << q.size() << ' '
    std::cout << " q.front() : " << q.front() << ' '
}


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;
}
  • 输出结果
758b516bfdcd8ed162fb120c0d764f66.jpeg


以上是关于使用C++手写队列 - Queue的主要内容,如果未能解决你的问题,请参考以下文章

C++ 标准模板库STL 队列 queue 使用方法与应用介绍

C++ 初阶优先级队列(Priority_Queue)底层框架模拟实现

C++ 初阶优先级队列(Priority_Queue)底层框架模拟实现

C++ STL:优先级队列priority_queue的使用方法和模拟实现

C++初阶----deque(双端队列)+stack queue模拟实现

C++ 知识回顾总结 -- queue 队列容器