📜  使用模板类和循环数组实现动态双端队列(1)

📅  最后修改于: 2023-12-03 15:22:24.907000             🧑  作者: Mango

使用模板类和循环数组实现动态双端队列

简介

双端队列是一种具有队列和栈的特性的数据结构,可以在队列的两端进行插入和删除操作。循环数组是一种实现队列数据结构的常用技术,可以避免频繁的内存分配和释放。本文将介绍如何使用模板类和循环数组来实现一个动态双端队列。

设计
类型定义
template <typename T>
class Deque {
public:
    Deque();
    ~Deque();
    void push_back(const T& elem);
    void pop_back();
    void push_front(const T& elem);
    void pop_front();
    bool empty() const;
    size_t size() const;
    T& back();
    T& front();
private:
    T* data_;
    int size_;
    int capacity_;
    int front_;
    int rear_;
    void expand();
};

在这个类中,我们定义了一个模板类Deque,模板参数表示队列中存储的数据类型。这个类有以下公有成员函数:

  • Deque():构造函数,初始化队列;
  • ~Deque():析构函数,销毁队列;
  • void push_back(const T& elem):将元素elem插入到队列的尾部;
  • void pop_back():删除队列尾部的元素;
  • void push_front(const T& elem):将元素elem插入到队列的头部;
  • void pop_front():删除队列头部的元素;
  • bool empty() const:判断队列是否为空;
  • size_t size() const:返回队列的大小;
  • T& back():返回队列尾部的元素;
  • T& front():返回队列头部的元素。

类中还定义了以下私有成员变量:

  • T* data_:指向存储数据的数组;
  • int size_:队列中元素的数量;
  • int capacity_:存储数据的数组的容量;
  • int front_:循环数组的头指针;
  • int rear_:循环数组的尾指针。
扩容

为了避免队列满时无法插入数据的情况,我们需要实现一个扩容函数来动态调整存储数据的数组的容量。我们可以每次将数组的容量翻倍来扩容,这样可以保证每次的时间复杂度为O(1)

template <typename T>
void Deque<T>::expand() {
    int new_capacity = capacity_ * 2;
    T* new_data = new T[new_capacity];
    for (int i = 0; i < size_; ++i) {
        new_data[i] = data_[(front_ + i) % capacity_];
    }
    front_ = 0;
    rear_ = size_;
    capacity_ = new_capacity;
    delete[] data_;
    data_ = new_data;
}

在这个函数中,我们首先将数组的容量翻倍,然后创建一个新的数组new_data来保存数据。我们将原数组中的元素复制到新数组中,并更新头指针和尾指针来指向新数组的头和尾。最后,我们释放原数组的内存,将指针指向新数组。

插入和删除操作

对于双端队列中的插入和删除操作,我们需要分别考虑头部和尾部的情况。当队列满时,我们需要调用扩容函数来增大数组的容量。具体实现如下:

template <typename T>
void Deque<T>::push_back(const T& elem) {
    if (size_ == capacity_) {
        expand();
    }
    data_[rear_] = elem;
    rear_ = (rear_ + 1) % capacity_;
    ++size_;
}

template <typename T>
void Deque<T>::pop_back() {
    if (empty()) {
        throw std::out_of_range("the deque is empty");
    }
    rear_ = (rear_ - 1 + capacity_) % capacity_;
    --size_;
}

template <typename T>
void Deque<T>::push_front(const T& elem) {
    if (size_ == capacity_) {
        expand();
    }
    front_ = (front_ - 1 + capacity_) % capacity_;
    data_[front_] = elem;
    ++size_;
}

template <typename T>
void Deque<T>::pop_front() {
    if (empty()) {
        throw std::out_of_range("the deque is empty");
    }
    front_ = (front_ + 1) % capacity_;
    --size_;
}

在插入和删除操作中,我们需要注意循环数组的特性。当插入尾部元素时,我们将元素插入到rear_指向的位置,并将rear_指针向后移动一位,如果rear_指针到了数组的尾部,则将其指向数组的头部。当删除尾部元素时,我们将rear_指针向前移动一位,并将size_的值减一。

当插入头部元素时,我们将元素插入到front_指向的位置,并将front_指针向前移动一位,如果front_指针到了数组的头部,则将其指向数组的尾部。当删除头部元素时,我们将front_指针向后移动一位,并将size_的值减一。

测试

我们可以使用以下代码来测试双端队列的实现:

#include <iostream>
#include <stdexcept>
#include "deque.h"

int main() {
    Deque<int> d;
    d.push_back(1);
    d.push_back(2);
    d.push_back(3);
    d.push_front(0);
    d.pop_back();
    d.pop_front();
    std::cout << d.front() << '\n';
    std::cout << d.back() << '\n';
    std::cout << d.size() << '\n';
    d.push_front(-1);
    std::cout << d.front() << '\n';
    std::cout << d.back() << '\n';
    std::cout << d.size() << '\n';
    return 0;
}

在这个示例中,我们创建了一个整型的双端队列,并向其插入一些元素。然后,我们分别删除队列的头部和尾部元素,并输出队列头部和尾部的元素,以及队列的大小。最后,我们再向队列的头部插入一个元素,再次输出队列头部和尾部的元素,以及队列的大小。

结论

本文介绍了如何使用模板类和循环数组来实现一个动态双端队列。这个双端队列具有队列和栈的特性,并支持在队列的两端进行插入和删除操作。通过使用循环数组来避免频繁的内存分配和释放,我们可以实现高效的双端队列。