一、概述
队列(queue): 只允许在一端进行插入 (队尾) 操作,而在另一端 (队头) 进行删除操作的线性表。
队头:删除操作的一端——front
队尾:插入操作的一端——rear
特点:先进先出(First In First Out)
其他常用队列:循环队列、阻塞队列、并发队列。
二、队列的抽象数据类型
ADT 队列 (Queue)
Data
同线性表。元素具有相同的类型,相邻元素具有前驱和后继关系
Opreation
InitQueue(*Q): 初始化操作,建立一个空队列Q。
DestroyQueue(*Q): 若队列Q存在,则销毁它。
ClearQueue(*Q): 将队列Q清空。
QueueEmpty(Q): 若队列为空,返回true,否则返回false。
GetHead(Q,*e): 若队列Q存在且非空
二、C++队列的方法
- back()——返回最后一个元素;
- empty()——如果队列空则返回true,否则返回false;
- front()——返回第一个元素;
- pop()——从队头删除第一个元素;
- push()——在队尾插入一个元素;
- size()——返回队列中元素的大小;
#include<iostream>
#include<queue>
using namespace std;
int main()
{
queue<int> que;
// 入队
for(int i = 0; i < 50; i++)
{
que.push(i);
}
cout<<"the size of queue:"<<que.size()<<endl;
while(!que.empty())
{
cout<<"the front element of queue:"<<que.front()<<" ";
cout<<"the rear element of queue:"<<que.back()<<endl;
que.pop();
}
cout<<"the size of queue:"<<que.size()<<endl;
return 0;
}
三、实现
1、顺序队列——数组实现
- 顺序队列需事先确定队列的大小,不支持动态分配存储空间,所以插入和删除元素比较省时,但是会造成空间的浪费。
解决方法:循环队列 =》解决空间浪费的问题
循环队列的实现:
#include <iostream>
using namespace std;
const int MAXSIZE = 1000;
typedef int ELEMTYPE;
const int N = 10;
typedef struct{
ELEMTYPE data[MAXSIZE];
int head; /*队头指针*/
int rear; /*队尾指针*/
}Queue;
Queue Q;
void initQueue(Queue &Q);
void printQueue(Queue &Q);
bool isQueueEmpty(Queue &Q);
bool isQueueFull(Queue &Q);
bool EnQueue(Queue &Q, ELEMTYPE e);
bool DeQueue(Queue &Q, ELEMTYPE &e);
int main()
{
for(int i = 0; i < N; i++)
{
EnQueue(Q, i);
}
printQueue(Q);
return 0;
}
void initQueue(Queue &Q)
{
Q.head = 0;
Q.rear = 0;
}
void printQueue(Queue &Q)
{
ELEMTYPE e;
while(!isQueueEmpty(Q))
{
DeQueue(Q,e);
cout<<e<<" ";
}
cout<<endl;
}
bool isQueueEmpty(Queue &Q)
{
if(Q.head == Q.rear)
return true;
else
return false;
}
bool isQueueFull(Queue &Q)
{
if((Q.rear+1)%MAXSIZE == Q.head)
return true;
else
return false;
}
bool EnQueue(Queue &Q, ELEMTYPE e)
{
if(isQueueFull(Q))
return false;
Q.rear = (Q.rear+1)%MAXSIZE;
Q.data[Q.rear] = e;
return true;
}
bool DeQueue(Queue &Q, ELEMTYPE &e)
{
if(isQueueEmpty(Q))
return false;
Q.head = (Q.head+1)%MAXSIZE;
e = Q.data[Q.head];
return true;
}
2、链式队列——链表实现
- 可以不需要事先知道队列的大小,支持动态和释放空间,但是插入和删除操作比较耗时
#include <iostream>
using namespace std;
struct NODE//双向链表基本单元结构
{
int data;
NODE *next;//后继指针
NODE *pre;//前驱指针
};
class QUEUE//定义queue类封装数据和实现
{
private:
NODE *front;//队头指针
NODE *tail;//队尾指针
unsigned size;
public:
QUEUE();
~QUEUE(){};
void initialize(); //初始化
void enqueue(int n); //入队
void dequeue(); //出队
int get_front(); //获取元素
void clear(); //清空队列
int get_size(); //返回元素个数
bool isempty(); //判断是否为空
void display_queue(); //输出队列
};
QUEUE::QUEUE()
{
initialize();
}
void QUEUE::initialize()
{
//初始化头部和尾部指针
front = new NODE();
tail = new NODE();
//将头尾连接
front->data = tail->data = 0;
front->pre = tail->next = NULL;
front->next = tail;
tail->pre = front;
size = 0; //设置元素个数为0
}
void QUEUE::enqueue(int n)
{
//开辟新节点
NODE *new_ele = new NODE();
//设置数据
new_ele->data = n;
//将新节点插入到双向链表尾部
tail->pre->next = new_ele;
new_ele->next = tail;
new_ele->pre = tail->pre;
tail->pre = new_ele;
size++; //元素个数加1
}
void QUEUE::dequeue()
{
if (isempty())//避开对空队列的操作
{
cout << "queue is empty" << endl;
return;
}
//获取删除将要删除的元素指针
NODE *temp = front->next;
front->next = temp->next;
temp->next->pre = front;
delete(temp);//释放内存
size--;
}
int QUEUE::get_front()
{
if (front->next != tail)
return front->next->data;
else
cout << "empty queque" << endl;
return -1;
}
void QUEUE::clear()
{
NODE *temp = front;
//遍历链表释放所有节点内存
while(temp != tail)
{
NODE *del_data = temp;
temp = temp->next;
delete(del_data);
}
//调用函数重新初始化
initialize();
}
int QUEUE::get_size()
{
return size;
}
void QUEUE::display_queue()
{
NODE *temp = front->next;
while (temp != tail)
{
cout << temp->data << " ";
temp = temp->next;
}
if (isempty())
cout << "queue is empty" << endl;
else
cout << endl;
}
bool QUEUE::isempty()
{
return front->next == tail;
}
int main(int argc, char const *argv[])
{
QUEUE que;
/* *do somthing here */
return 0;
}
3、循环队列
- 关键:判断队列是空对还是满队
- 空:head == tail
- 满:(tail+1)%n == head
- 当循环队列满队时,tail指针指向的位置实际并没有存储数据。=》循环队列会浪费一个数组的存储空间
template<class T>
class SeqQueue{
protected:
T *element;
int front,rear;
int maxSize;
public:
SeqQueue(int sz=10){
front=rear=0;
maxSize=sz;
element=new T[maxSize];
}
~SeqQueue(){
delete[] element;
}
bool EnQueue(const T& x){//入队
if(isFull()) return false;
element[rear]=x;
rear=(rear+1)%maxSize;
return true;
}
bool DeQueue(T& x){//出队
if(isEmpty()) return false;
x=element[front];
front=(front+1)%maxSize;
return true;
}
bool getFront(T& x){//获取队首元素
if(isEmpty()) return false;
x=element[front];
return true;
}
void makeEmpty(){//队列置空
front=rear=0;
}
bool isEmpty()const{//判断队列是否为空
return (rear==front)?true:false;
}
bool isFull()const{//队列是否为满
return ((rear+1)%maxSize==front)?true:false;
}
int getSize()const{
return (rear-front+maxSize)%maxSize;
}
};
4、阻塞队列
支持阻塞操作的队列。具体来讲,支持阻塞添加和阻塞移除。
阻塞添加: 当队列满的时候,队列会阻塞插入插入的元素的线程,直到队列不满;
阻塞移除: 在队列为空时,队里会阻塞插入元素的线程,直到队列不满。
阻塞队列常用于“生产者-消费者模型”,生产者是向队列添加元素的线程;消费者是从队列取元素的线程。
5、并发队列
并发队列就是队列的操作多线程安全。
实现:基于数组的循环队列,利用CAS原子操作,可以实现非常高效的并发队列