数据结构——双端队列
我们今天来看队列的变形——双端队列:
什么是双端队列
双端队列(Double-Ended Queue, 简称deque)是一种特殊的数据结构,它结合了队列(Queue)和栈(Stack)的特性,允许在两端进行插入(enqueue)和删除(dequeue)操作。双端队列可以在前端(front)进行入队(push)和出队(pop),也可以在后端(rear)进行入队和出队。这种灵活性使得双端队列在很多应用场景中非常实用,特别是在需要高效地处理双向数据流或需要快速访问两端元素的场合。
基本特征:
常见操作:
实现双端队列的数据结构可以是数组、链表或更复杂的数据结构。使用数组实现时,可能需要进行动态扩容或移动元素以维持高效的插入和删除操作。链表实现则更为灵活,无需考虑连续内存空间的限制,但访问性能可能略逊于数组实现。现代编程语言中通常有内置的双端队列数据结构或第三方库提供高效实现。
总之,双端队列是一种提供两端插入和删除能力的数据结构,它结合了队列和栈的优点,适用于需要高效处理双向数据流或频繁操作两端元素的场景。
在C++中,双向队列,在头文件deque中:
我们可以使用一下它的接口:
#include <iostream>
#include<deque>
void PrintDque(const std::deque<int>& dq)
{
std::deque<int>::const_iterator it = dq.cbegin();
while( it != dq.cend())
{
std::cout << *it++ << ' ';
}
std::cout<<std::endl;
}
int main()
{
std::deque<int> dq;
dq.push_back(12);
dq.push_back(13);
dq.push_back(14);
dq.push_back(15);
PrintDque(dq);
dq.push_front(16);
dq.push_front(17);
dq.push_front(18);
dq.push_front(19);
PrintDque(dq);
dq.pop_back();
dq.pop_back();
dq.pop_back();
dq.pop_back();
PrintDque(dq);
dq.pop_front();
dq.pop_front();
dq.pop_front();
dq.pop_front();
PrintDque(dq);
if(dq.empty())
{
std::cout << "dque is empty" << std::endl;
}
return 0;
}
双端队列的实现
为了节省时间,我们这里使用vector来作为deque的底层结构:
#include<iostream>
#include<vector>
// 定义一个名为 Deque(双端队列)的类
class Deque{
public:
// 默认构造函数,创建一个空的双端队列
Deque() = default;
// 返回一个布尔值,表示当前双端队列是否为空
bool is_empty() const
{
return items.empty();
}
// 在双端队列的前端添加一个整数元素(item)
void add_front(int item)
{
items.insert(items.begin(), item); // 使用 insert 函数将新元素插入到 vector 开头
}
// 在双端队列的后端添加一个整数元素(item)
void add_rear(int item)
{
items.push_back(item); // 使用 push_back 函数将新元素添加到 vector 末尾
}
// 从双端队列的前端移除并返回一个整数元素
int remove_front()
{
if (is_empty()) // 检查队列是否为空
{
return -1; // 若为空,返回 -1 表示操作失败
}
int front_item = items.front(); // 获取队列前端元素的值
items.erase(items.begin()); // 使用 erase 函数移除 vector 开头的元素
return front_item; // 返回移除的元素值
}
// 从双端队列的后端移除并返回一个整数元素
int remove_rear()
{
if (is_empty()) // 检查队列是否为空
{
return -1; // 若为空,返回 -1 表示操作失败
}
int rear_item = items.back(); // 获取队列后端元素的值
items.pop_back(); // 使用 pop_back 函数移除 vector 末尾的元素
return rear_item; // 返回移除的元素值
}
private:
// 使用 std::vector 存储双端队列中的整数元素
std::vector<int> items;
};
int main()
{
Deque deque;
deque.add_front(1);
deque.add_front(2);
deque.add_rear(3);
deque.add_rear(4);
std::cout << "Deque front: " << deque.remove_front()<< std::endl;
std::cout << "Deque rear: " << deque.remove_rear()<< std::endl;
return 0;
}
双端队列的使用场景
双端队列作为一种特殊的数据结构,具有在两端同时进行插入和删除操作的能力,这使得它适用于多种需要灵活增删元素的场景。以下是双端队列的一些典型使用场景:
总之,双端队列因其独特的双端操作特性,适用于任何需要在数据结构两端高效插入和删除元素的场景,特别是在处理动态数据流、历史记录管理、任务调度和搜索算法中表现出色。
下面是双端队列的使用场景:
这些算法题目中,双端队列可以帮助我们高效地管理数据,尤其是在需要快速访问序列的两端或者维护一个有序序列的时候。
比如说力扣上的滑动窗口的最大值:
用C++的话,可以这样写:
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
std::deque<int> dq;
std::vector<int> result;
for (int i = 0; i < nums.size(); ++i) {
// 移除队列中不在滑动窗口范围内的元素
if (!dq.empty() && dq.front() == i - k) {
dq.pop_front();
}
// 从队列尾部移除小于当前元素的元素
while (!dq.empty() && nums[dq.back()] < nums[i]) {
dq.pop_back();
}
// 将当前元素的索引插入队列
dq.push_back(i);
// 当滑动窗口完全在数组范围内时,将队列头部的最大值添加到结果数组中
if (i >= k - 1) {
result.push_back(nums[dq.front()]);
}
}
return result;
}
};