目录
一、队列的简介
队列(Queue)是一种线性数据结构,其特点是数据的插入和删除操作分别在不同的两端进行:插入操作在队列的尾部,删除操作在队列的头部。因此,队列遵循先进先出(FIFO, First In First Out)的原则,即最早进入队列的元素最先被处理。
1.队列的基本操作
- enqueue(入队):向队列的尾部添加一个元素。
- dequeue(出队):返回队列头部的元素,且移除该元素。
- front(取队头):返回队列头部的元素,但不移除该元素。
- empty(判空):判断队列中是否有元素。
2.队列的分类
根据队列的不同用途和实现方式,队列可以分为几类:
-
普通队列(Simple Queue):最常见的队列类型,插入操作在队尾,删除操作在队头,按先进先出的顺序执行。
-
循环队列(Circular Queue):在普通队列的基础上,当队尾到达队列末端时,如果队头前面有空位,队尾可以绕回到队列的开头。这种实现方式能够更有效地利用存储空间,避免“假溢出”问题(队尾指针到达末尾但队列仍有空间)。
-
双端队列(Deque, Double-Ended Queue):允许在队列的两端进行插入和删除操作。根据操作限制的不同,双端队列可以进一步分为以下几种:
- 输入受限双端队列:只能从一端进行插入,但可以从两端删除。
- 输出受限双端队列:可以从两端插入,但只能从一端删除。
-
优先队列(Priority Queue):队列中的每个元素都有一个优先级,删除元素时并非按照进入队列的顺序,而是按照优先级高低来决定出队的顺序。通常,优先级高的元素会先被处理。
3.队列的应用场景
队列作为一种基础的数据结构,广泛应用于各种场景中,尤其是在需要按序处理任务的场景下。常见的应用场景有:
- 操作系统中的任务调度:操作系统通过队列管理进程或线程的执行顺序,按时间片调度进程。
- 广度优先搜索(BFS):在图的广度优先遍历中,使用队列来管理待访问的节点。
- 缓存(Buffer)系统:队列被用于处理生产者-消费者问题,如输入输出缓冲、数据流缓冲。
- 消息队列(Message Queue):在分布式系统或异步任务处理系统中,队列用于存储和分发消息,保证消息的按序处理。
二、顺序队列的实现
这里介绍循环队列的实现。
- 构造函数:将队头指针peek指向数组低端0,队尾指针rear指向-1。
- 析构函数:循环队列是静态存储分配,在循环队列变量退出作用域时自动释放所占内存单元,因此循环队列无需销毁,析构函数为空。
- 入队操作:队尾指针rear循环意义下+1,然后将待插元素插入队尾即可。
- 出队操作:取出队头,然后队头指针peek循环意义下+1即可。
- 取对头操作:直接取出对头元素即可。
- 判空操作:判断队列元素个数是否为0即可。
template <typename T> //使用模板,兼容各种数据类型
class Cirqueue
{
public:
Cirqueue() //构造函数
{
rear = -1;
peek = 0;
count = 0;
}
~Cirqueue() {} //析构函数
void enqueue(T x) //入队
{
if (count==size)
{
throw "上溢"; //队满,防上溢
}
rear = (rear + 1) % size;
//队尾指针在循环意义下+1
data[rear] = x;
//在队尾存入数据
count++;
//元素个数+1
}
T dequeue() //出队
{
if (count==0)
{
throw "下溢"; //队空,防下溢
}
T temp = data[peek];
//暂存队头数据
peek = (peek + 1) % size;
//队头指针在循环意义下+1
count--;
//元素个数-1
return temp;
}
T front() //取队头
{
if (count==0)
{
throw "下溢"; //队空,防下溢
}
return data[peek];
}
bool empty() //判空
{
return count == 0;
}
private:
const static int size = 100000; //队列长度
T data[size]; //用于存储数据的数组
int peek; //队头指针
int rear; //队尾指针
int count; //队列元素计数
};
说明:循环意义下是指在一个范围内变化,如x%size的值在[0,size-1]之间变化。
三、链队列的实现
这里介绍链队列的实现。
- 构造函数 :将队头指针peek和队尾指针rear都初始化为空指针nullptr。
- 析构函数:链队列是动态存储分配,需要释放链队列的所有结点的存储空间。
- 入队操作:只考虑在链表的尾部进行。
- 出队操作:只考虑在链表的头部进行。
- 取队头操作:直接通过队头指针取队头元素。
- 判空操作:只需判断count是否为0。
- 队列大小:只需返回count。
template <typename T> //使用模板,兼容各种数据类型
class LinkQueue
{
public:
LinkQueue() //构造函数
{
peek = nullptr;
rear = nullptr;
count = 0;
}
~LinkQueue() //析构函数
{
while (!empty())
{
dequeue();
//将结点逐个释放
}
}
void enqueue(T x) //入队
{
Node* s = new Node;
if (rear == nullptr)
{
s->data = x;
s->next = nullptr;
peek = rear = s;
// 如果队列为空,peek和rear都指向新结点
}
else
{
s->data = x;
s->next = nullptr;
rear->next = s;
rear = s;
//如果队列非空,新结点插入队尾
}
count++;
}
T dequeue() //出队
{
if (count == 0)
{
throw "队列下溢";
}
Node* p = peek;
// 暂存当前的队头节点
T temp = peek->data;
// 获取队头节点的数据
peek = peek->next;
// 更新队头指针,指向下一个节点
if (peek == nullptr)
{
rear = nullptr;
// 如果队列变为空,则将rear也设置为nullptr
}
delete p;
// 释放原队头节点的内存
count--;
return temp;
}
T front() const //取队头
{
if (count == 0)
{
throw "队列下溢";
}
return peek->data;
}
bool empty() const //判空
{
return count == 0;
}
int size() const //查元素个数
{
return count;
}
private:
struct Node
{
T data; // 数据域
Node* next; // 指针域
};
Node* peek; // 队头指针
Node* rear; // 队尾指针
int count; // 队列元素计数
};
四、STL库中的队列
队列这样的数据结构已经在STL库中定义好了,如果没有自行设计队列的要求,可以直接使用库中定义好的队列。STL库中有单端队列和双端队列,现简单介绍其用法如下:
#include <queue> //单端队列,使用STL库中的队列需要包含相关头文件
queue<char> q; //定义一个char类型的单端队列,命名为q
q.push('a'); //入队
q.pop(); //出队
q.back(); //取队尾
q.front(); //取队头
q.empty(); //判空,返回bool值
q.size(); //查询队列元素个数
/***************************************************************************/
#include <deque> //双端队列,使用STL库中的队列需要包含相关头文件
deque<char> d; //定义一个char类型的双端队列,命名为d
d.push_front('a'); //在队头入队
d.push_back('b'); //在队尾入队
d.pop_front(); //在队头出队
d.pop_back(); //在队尾出队
d.back(); //取队尾
d.front(); //取队头
d.empty(); //判空,返回bool值
d.size(); //查看队列元素个数