手写双端对列
2712 字
14 分钟
手写双端对列
1. 双端队列 (Deque) 概述
双端队列(Double-Ended Queue,简称 deque)是一种允许在其两端进行高效插入和删除操作的数据结构。与普通队列(只允许在一端插入和另一端删除)相比,双端队列更为灵活。
C++ 标准库中已经提供了 std::deque,但通过自行实现一个双端队列,可以更好地理解其内部机制和迭代器的工作原理。
2. 实现思路
为了实现一个高效的双端队列,我们需要考虑以下几点:
- 动态数组:使用动态数组(如环形缓冲区)来存储元素,以便支持在两端进行常数时间的插入和删除。
- 头尾指针:维护头部和尾部的索引,以便快速访问两端。
- 自动扩展:当容量不足时,自动调整内部缓冲区的大小。
- 迭代器支持:定义一个迭代器类,允许用户使用像
begin()和end()这样的函数进行遍历。
接下来,我们将一步步实现这些功能。
3. 详细实现
3.1 内部数据结构
我们将使用一个动态分配的数组作为内部缓冲区,并通过头尾索引来管理队列的前后端。为了支持在两端高效插入和删除,我们将采用环形缓冲区的概念,即当索引达到数组的末端时,自动回绕到数组的开头。
3.2 Deque 类
下面是 Deque 类的基本结构和关键成员:
#include <iostream>#include <stdexcept>#include <iterator>
template <typename T>class Deque {private: T* buffer; // 内部缓冲区 size_t capacity; // 缓冲区容量 size_t front_idx; // 头部索引 size_t back_idx; // 尾部索引 size_t count; // 当前元素数量
// 调整容量 void resize(size_t new_capacity) { T* new_buffer = new T[new_capacity]; // 重新排列元素 for (size_t i = 0; i < count; ++i) { new_buffer[i] = buffer[(front_idx + i) % capacity]; } delete[] buffer; buffer = new_buffer; capacity = new_capacity; front_idx = 0; back_idx = count; }
public: // 构造函数 Deque(size_t initial_capacity = 8) : capacity(initial_capacity), front_idx(0), back_idx(0), count(0) { buffer = new T[capacity]; }
// 析构函数 ~Deque() { delete[] buffer; }
// 复制构造函数和赋值运算符(省略,为简洁起见)
// 检查是否为空 bool empty() const { return count == 0; }
// 获取大小 size_t size() const { return count; }
// 在前面插入元素 void push_front(const T& value) { if (count == capacity) { resize(capacity * 2); } front_idx = (front_idx == 0) ? capacity - 1 : front_idx - 1; buffer[front_idx] = value; ++count; }
// 在后面插入元素 void push_back(const T& value) { if (count == capacity) { resize(capacity * 2); } buffer[back_idx] = value; back_idx = (back_idx + 1) % capacity; ++count; }
// 从前面删除元素 void pop_front() { if (empty()) { throw std::out_of_range("Deque is empty"); } front_idx = (front_idx + 1) % capacity; --count; }
// 从后面删除元素 void pop_back() { if (empty()) { throw std::out_of_range("Deque is empty"); } back_idx = (back_idx == 0) ? capacity - 1 : back_idx - 1; --count; }
// 获取前端元素 T& front() { if (empty()) { throw std::out_of_range("Deque is empty"); } return buffer[front_idx]; }
const T& front() const { if (empty()) { throw std::out_of_range("Deque is empty"); } return buffer[front_idx]; }
// 获取后端元素 T& back() { if (empty()) { throw std::out_of_range("Deque is empty"); } size_t last_idx = (back_idx == 0) ? capacity - 1 : back_idx - 1; return buffer[last_idx]; }
const T& back() const { if (empty()) { throw std::out_of_range("Deque is empty"); } size_t last_idx = (back_idx == 0) ? capacity - 1 : back_idx - 1; return buffer[last_idx]; }
// 迭代器类将放在这里(见下一部分)
// 迭代器类定义 class Iterator { private: Deque<T>* deque_ptr; size_t index; size_t pos;
public: using iterator_category = std::bidirectional_iterator_tag; using value_type = T; using difference_type = std::ptrdiff_t; using pointer = T*; using reference = T&;
Iterator(Deque<T>* deque, size_t position) : deque_ptr(deque), pos(position) {}
// 解引用操作 reference operator*() const { size_t real_idx = (deque_ptr->front_idx + pos) % deque_ptr->capacity; return deque_ptr->buffer[real_idx]; }
pointer operator->() const { size_t real_idx = (deque_ptr->front_idx + pos) % deque_ptr->capacity; return &(deque_ptr->buffer[real_idx]); }
// 前置递增 Iterator& operator++() { ++pos; return *this; }
// 后置递增 Iterator operator++(int) { Iterator temp = *this; ++pos; return temp; }
// 前置递减 Iterator& operator--() { --pos; return *this; }
// 后置递减 Iterator operator--(int) { Iterator temp = *this; --pos; return temp; }
// 比较操作 bool operator==(const Iterator& other) const { return (deque_ptr == other.deque_ptr) && (pos == other.pos); }
bool operator!=(const Iterator& other) const { return !(*this == other); } };
// 获取 begin 迭代器 Iterator begin() { return Iterator(this, 0); }
// 获取 end 迭代器 Iterator end() { return Iterator(this, count); }};3.3 迭代器类
在上面的 Deque 类中,我们定义了一个嵌套的 Iterator 类。这个迭代器支持前向和后向遍历,并且可以与标准的 C++ 迭代器兼容。
关键点解释:
- 成员变量:
deque_ptr:指向包含此迭代器的Deque实例。pos:相对于队列头部的位置。
- 重载运算符:
operator*和operator->:用于访问当前元素。operator++和operator--:前置和后置递增和递减,用于移动迭代器。operator==和operator!=:用于比较两个迭代器是否相同。
- 注意事项:
- 迭代器并不管理元素的生命周期,只是提供遍历接口。
- 迭代器的有效性依赖于队列的修改操作(如插入和删除)。在实际应用中,需要注意迭代器失效的问题。
4. 使用示例
下面是一个使用上述 Deque 类及其迭代器的示例程序:
#include <iostream>#include <string>
// 假设 Deque 类已经定义在这里
int main() { Deque<std::string> dq;
// 在后面插入元素 dq.push_back("Apple"); dq.push_back("Banana"); dq.push_back("Cherry");
// 在前面插入元素 dq.push_front("Date"); dq.push_front("Elderberry");
// 显示队列大小 std::cout << "Deque 大小: " << dq.size() << std::endl;
// 使用迭代器进行遍历 std::cout << "Deque 元素: "; for (auto it = dq.begin(); it != dq.end(); ++it) { std::cout << *it << " "; } std::cout << std::endl;
// 访问前端和后端元素 std::cout << "前端元素: " << dq.front() << std::endl; std::cout << "后端元素: " << dq.back() << std::endl;
// 删除元素 dq.pop_front(); dq.pop_back();
// 再次遍历 std::cout << "删除元素后的 Deque: "; for (auto it = dq.begin(); it != dq.end(); ++it) { std::cout << *it << " "; } std::cout << std::endl;
return 0;}预期输出
Deque 大小: 5Deque 元素: Elderberry Date Apple Banana Cherry前端元素: Elderberry后端元素: Cherry删除元素后的 Deque: Date Apple Banana解释:
- 插入操作:
- 使用
push_back在队列的后端插入 “Apple”, “Banana”, “Cherry”。 - 使用
push_front在队列的前端插入 “Date”, “Elderberry”。 - 最终队列顺序为:Elderberry, Date, Apple, Banana, Cherry
- 使用
- 遍历操作:
- 使用迭代器从
begin()到end()遍历并打印所有元素。
- 使用迭代器从
- 访问元素:
- 使用
front()获取队列前端的元素。 - 使用
back()获取队列后端的元素。
- 使用
- 删除操作:
- 使用
pop_front删除前端元素(“Elderberry”)。 - 使用
pop_back删除后端元素(“Cherry”)。 - 删除后,队列顺序为:Date, Apple, Banana
- 使用
5. 完整代码
以下是完整的 Deque 类及其使用示例代码:
#include <iostream>#include <stdexcept>#include <iterator>
// Deque 类定义template <typename T>class Deque {private: T* buffer; // 内部缓冲区 size_t capacity; // 缓冲区容量 size_t front_idx; // 头部索引 size_t back_idx; // 尾部索引 size_t count; // 当前元素数量
// 调整容量 void resize(size_t new_capacity) { T* new_buffer = new T[new_capacity]; // 重新排列元素 for (size_t i = 0; i < count; ++i) { new_buffer[i] = buffer[(front_idx + i) % capacity]; } delete[] buffer; buffer = new_buffer; capacity = new_capacity; front_idx = 0; back_idx = count; }
public: // 构造函数 Deque(size_t initial_capacity = 8) : capacity(initial_capacity), front_idx(0), back_idx(0), count(0) { buffer = new T[capacity]; }
// 析构函数 ~Deque() { delete[] buffer; }
// 禁用复制构造函数和赋值运算符(为了简洁,可根据需要实现) Deque(const Deque& other) = delete; Deque& operator=(const Deque& other) = delete;
// 检查是否为空 bool empty() const { return count == 0; }
// 获取大小 size_t size() const { return count; }
// 在前面插入元素 void push_front(const T& value) { if (count == capacity) { resize(capacity * 2); } front_idx = (front_idx == 0) ? capacity - 1 : front_idx - 1; buffer[front_idx] = value; ++count; }
// 在后面插入元素 void push_back(const T& value) { if (count == capacity) { resize(capacity * 2); } buffer[back_idx] = value; back_idx = (back_idx + 1) % capacity; ++count; }
// 从前面删除元素 void pop_front() { if (empty()) { throw std::out_of_range("Deque is empty"); } front_idx = (front_idx + 1) % capacity; --count; }
// 从后面删除元素 void pop_back() { if (empty()) { throw std::out_of_range("Deque is empty"); } back_idx = (back_idx == 0) ? capacity - 1 : back_idx - 1; --count; }
// 获取前端元素 T& front() { if (empty()) { throw std::out_of_range("Deque is empty"); } return buffer[front_idx]; }
const T& front() const { if (empty()) { throw std::out_of_range("Deque is empty"); } return buffer[front_idx]; }
// 获取后端元素 T& back() { if (empty()) { throw std::out_of_range("Deque is empty"); } size_t last_idx = (back_idx == 0) ? capacity - 1 : back_idx - 1; return buffer[last_idx]; }
const T& back() const { if (empty()) { throw std::out_of_range("Deque is empty"); } size_t last_idx = (back_idx == 0) ? capacity - 1 : back_idx - 1; return buffer[last_idx]; }
// 迭代器类定义 class Iterator { private: Deque<T>* deque_ptr; size_t pos;
public: using iterator_category = std::bidirectional_iterator_tag; using value_type = T; using difference_type = std::ptrdiff_t; using pointer = T*; using reference = T&;
Iterator(Deque<T>* deque, size_t position) : deque_ptr(deque), pos(position) {}
// 解引用操作 reference operator*() const { size_t real_idx = (deque_ptr->front_idx + pos) % deque_ptr->capacity; return deque_ptr->buffer[real_idx]; }
pointer operator->() const { size_t real_idx = (deque_ptr->front_idx + pos) % deque_ptr->capacity; return &(deque_ptr->buffer[real_idx]); }
// 前置递增 Iterator& operator++() { ++pos; return *this; }
// 后置递增 Iterator operator++(int) { Iterator temp = *this; ++pos; return temp; }
// 前置递减 Iterator& operator--() { --pos; return *this; }
// 后置递减 Iterator operator--(int) { Iterator temp = *this; --pos; return temp; }
// 比较操作 bool operator==(const Iterator& other) const { return (deque_ptr == other.deque_ptr) && (pos == other.pos); }
bool operator!=(const Iterator& other) const { return !(*this == other); } };
// 获取 begin 迭代器 Iterator begin() { return Iterator(this, 0); }
// 获取 end 迭代器 Iterator end() { return Iterator(this, count); }};
// 使用示例int main() { Deque<std::string> dq;
// 在后面插入元素 dq.push_back("Apple"); dq.push_back("Banana"); dq.push_back("Cherry");
// 在前面插入元素 dq.push_front("Date"); dq.push_front("Elderberry");
// 显示队列大小 std::cout << "Deque 大小: " << dq.size() << std::endl;
// 使用迭代器进行遍历 std::cout << "Deque 元素: "; for (auto it = dq.begin(); it != dq.end(); ++it) { std::cout << *it << " "; } std::cout << std::endl;
// 访问前端和后端元素 std::cout << "前端元素: " << dq.front() << std::endl; std::cout << "后端元素: " << dq.back() << std::endl;
// 删除元素 dq.pop_front(); dq.pop_back();
// 再次遍历 std::cout << "删除元素后的 Deque: "; for (auto it = dq.begin(); it != dq.end(); ++it) { std::cout << *it << " "; } std::cout << std::endl;
return 0;}编译和运行
保存上述代码到一个文件,例如 DequeWithIterator.cpp,然后使用 C++ 编译器进行编译和运行:
g++ -std=c++11 -o DequeWithIterator DequeWithIterator.cpp./DequeWithIterator预期输出
Deque 大小: 5Deque 元素: Elderberry Date Apple Banana Cherry前端元素: Elderberry后端元素: Cherry删除元素后的 Deque: Date Apple Banana6. 总结
通过上述步骤,我们成功实现了一个支持双端插入和删除的双端队列(deque),并添加了迭代器支持,使其能够与标准的 C++ 迭代器接口兼容。这个实现包含了以下关键点:
- 内部缓冲区管理:
- 使用动态数组并采用环形缓冲区的方式,支持高效的双端操作。
- 自动调整缓冲区的容量,确保在元素数量增加时仍能保持高效。
- 迭代器实现:
- 定义了一个嵌套的
Iterator类,支持前向和后向遍历。 - 重载了必要的运算符(如
*,->,++,--,==,!=),以实现与标准迭代器的兼容。
- 定义了一个嵌套的
- 基本操作:
push_front和push_back:分别在队列的前端和后端插入元素。pop_front和pop_back:分别从队列的前端和后端删除元素。front和back:访问队列的前端和后端元素。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!
相关文章智能推荐
1
map用法及手写map算法
C++2026-05-26
2
手写线程安全智能指针
C++2026-05-25
3
stream流
C++2026-06-01
4
单例模式
C++2026-05-31
5
万能引用和原样转发
C++2026-05-30
随机文章随机推荐







