队列
队列是一种受限的线性表,(Queue),它是一种运算受限的线性表,规则是先进先出;
1.队列是一种受限的线性结构
2.它只允许在表的前端(front)进行删除,在表的后端(rear)进行插入操作;
数组实现队列:
#include <iostream>
#include <string>
using namespace std;
//创建一个数组队列
typedef int DATA_type;
#define MAX_SIZE 5
typedef struct{
DATA_type data[MAX_SIZE];//数组
int fount;//第一个
int reat;//最后一个
}Que;
//初始化数组队列
bool Init_Que(Que* sq) {
if(!sq){
return false;
}
sq->fount = sq->reat = 0;
return true;
}
//判断数组队列是否为空
bool Judge_Que_NULL(Que* sq) {
if (!sq) {
return false;
}
if (sq->fount == sq->reat) {
return true;
}
return false;
}
//判断数组队列是否已满
bool Judge_Que_FULL(Que* sq) {
if (!sq) {
return false;
}
sq->fount = (sq->reat + 1) % MAX_SIZE;
return true;
}
//打印数组队列
void Print_Que(Que* sq) {
if (!sq) {
return;
}
for (int i = sq->fount; i < sq->reat; i++) {
cout << sq->data[i] << ",";
}
cout << endl;
}
//入队
bool Insert_Que(Que* sq, DATA_type elem) {
if (!sq) {
return false;
}
if (Judge_Que_FULL(sq)) {
return false;
}
sq->data[sq->reat] = elem;
sq->reat++;
return true;
}
//出队
bool Delete_Que_fout(Que* sq, DATA_type* data) {
if (!sq || Judge_Que_NULL(sq)) {
return false;
}
if (sq->fount ==sq->reat) {
return false;
}
*data = sq->data[sq->fount];
sq->fount = sq->fount + 1;
return true;
}
//出队
bool Delete_Que_back(Que* sq, DATA_type* data) {
if (!sq || Judge_Que_NULL(sq)) {
return false;
}
if (!data) {
return false;
}
*data = sq->data[sq->fount];
for (int i = sq->fount + 1; i < sq->reat; i++) {
sq->data[i - 1] = sq->data[i];
}
sq->reat--;
return true;
}
//清空队列
void Clear_Que(Que* sq) {
if (!sq) {
return;
}
sq->fount = sq->reat = 0;
}
//获取首元素的数据
int Getfount(Que* sq) {
if (!sq) {
return 0;
}
return sq->data[sq->fount];
}
//获取队列中的元素个数
int Getlength(Que* sq) {
if (!sq) {
return 0;
}
return sq->reat - sq->fount;
}
链表实现队列:
//创建一个链表队列,链表队列也叫动态队列
typedef int DataType;//队列中的元素
typedef struct _QNode{
//队列节点的结构体
DataType data;
struct _QNode* next;
}QNode;
typedef QNode* QuePtr;//使用类型转换方便后期维护
typedef struct {
int length;//队列的长度
QuePtr front;//队列头指针
QuePtr rear;//队列尾指针
}LinkQue;
//初始化队列链表
void initQue(LinkQue* LQ) {
if (!LQ) {
return;
}
LQ->length = 0;
LQ->front = LQ->rear = NULL;//头尾指针同时为空
}
//判断队列是否为空
int IsEmpty(LinkQue* LQ) {
if (!LQ) {
return 0;
}
if (LQ->front == NULL) {
return 1;
}
return 0;
}
//判断队列是否为满
int IsFull(LinkQue* LQ) {
if (!LQ) {
return 0;
}
if (LQ->length == MAX_SIZE) {
return 1;
}
return 0;
}
//入队
int EnterQue(LinkQue* LQ, DataType data) {
if (!LQ) {
return 0;
}
if (IsFull(LQ)) {
return 0;
}
//定义一个新节点
QNode* node = new QNode;
node->data = data;
node->next = NULL;
if (IsEmpty(LQ)) {//空队列
LQ->front = LQ->rear = node;
}
else {
LQ->rear->next = node;//在队尾插入节点node
LQ->rear = node;//队尾指向新插入的节点
}
LQ->length++;
return 1;
}
//出队
int DeleteQue(LinkQue* LQ, DataType data) {
if (!LQ || IsEmpty(LQ)||!data) {
return 0;
}
QNode* tmp = NULL;
tmp = LQ->front;
LQ->front = tmp->next;
if (!LQ->front) {
//如果队头出列后不存在其他元素,rear节点也要置空
LQ->rear = NULL;
}
data = tmp->data;
LQ->length--;
delete[] tmp;
return 1;
}
//打印队列中的元素
void PrintQue(LinkQue* LQ) {
if (!LQ) {
return;
}
if (LQ->front == NULL) {
cout << "队列为空!" << endl;
return;
}
QuePtr tmp;
tmp = LQ->front;
while (tmp) {
cout << tmp->data << ",";
tmp = tmp->next;
}
cout << endl;
}
//获取队首元素
int GetHead(LinkQue* LQ, DataType data) {
if (!LQ || IsEmpty(LQ)||!data) {
return 0;
}
data = LQ->front->data;
return 1;
}
//获取队尾元素
int GetEnd(LinkQue* LQ, DataType data) {
if (!LQ || IsEmpty(LQ) || !data) {
return 0;
}
data = LQ->rear->data;
return 1;
}
//清空队列
void ClearQue(LinkQue* LQ) {
if (!LQ) {
return;
}
while (LQ->front) {
//定义一个临时指针变量,指向队首的下一个节点
QuePtr tmp = LQ->front->next;
//释放队首的节点
delete[] LQ->front;
//继续往后清空
LQ->front = tmp;
}
LQ->front = LQ->rear = NULL;
LQ->length = 0;
}
//获取队列中的元素个数
int GetLength(LinkQue* LQ) {
if (!LQ) {
return 0;
}
return LQ->length;
}
队列在企业级应用的案例:
任务队列
线程池中的任务队列: 线程池是由任务队列和一组处理队列的线程组成,一旦工作进程需要处理某个可能"阻塞"的操作,不用自己操作,将其作为一个任务放到线程池的队列,接着会被某个空闲线程提取处理;
模拟代码:
#include <iostream>
#include <Windows.h>
using namespace std;
//队列的最大容量
#define MAX_SZIE 64
//任务节点的结构体
typedef struct _QNode {
int id;//任务的编号
void (*handler)(void);//执行的任务函数
struct _QNode* next;//指向下一个节点的指针
}QNode;
typedef QNode* QuePtr;
//队列的结构体
typedef struct Que {
int length;//队列的长度
QuePtr front;//队首指针
QuePtr rear;//队尾指针
}LinkQue;
//分配线程执行的任务节点
QuePtr thread_task() {
QNode* task;
task = new QNode;
if (!task) {
return NULL;
}
return task;
}
//队列初始化,将队列初始化为空队列
void initQue(LinkQue* LQ) {
if (!LQ) {
return;
}
LQ->length = 0;
LQ->front = LQ->rear = NULL;
}
//判断队列是否为空
int IsNull(LinkQue* LQ) {
if (!LQ) {
return 0;
}
if (LQ->front == NULL) {
return 1;
}
return 0;
}
//判断队列是否已满
int IsFull(LinkQue* LQ) {
if (!LQ) {
return 0;
}
if (LQ->length == MAX_SZIE) {
return 1;
}
return 0;
}
//入队,将元素data插入到队列LQ中
int EnterQue(LinkQue* LQ, QNode* data) {
if (!LQ || !data) {
return 0;
}
if (IsFull(LQ)) {
return 0;
}
data->next = NULL;
if (IsNull(LQ)) {
LQ->front = LQ->rear = data;
}
else {
LQ->rear->next = data;
LQ->rear = data;
}
LQ->length++;
return 1;
}
//出队,将队列中队头的节点出队,返回头节点
QNode* PopQue(LinkQue* LQ) {
QNode* tmp = NULL;
if (!LQ || IsNull(LQ)) {
return 0;
}
tmp = LQ->front;
LQ->front = tmp->next;
if (!LQ->front) {
LQ->rear = NULL;
}
LQ->length--;
return tmp;
}
//打印队列中的各元素
void PrintQue(LinkQue* LQ) {
QuePtr tmp;
if (!LQ) {
return;
}
if (LQ->front == NULL) {
return;
}
tmp = LQ->front;
while (tmp) {
cout << tmp->id << ",";
tmp = tmp->next;
}
cout << endl;
}
//获取队列中的元素个数
int GetLength(LinkQue* LQ) {
if (!LQ) {
return 0;
}
return LQ->length;
}
//测试任务
void test1() {
cout << "测试任务1" << endl;
}
void test2() {
cout << "测试任务2" << endl;
}
void test3() {
cout << "测试任务3" << endl;
}
//测试函数
int main4(void) {
LinkQue* LQ = new LinkQue;
QNode* task = NULL;
initQue(LQ);
//任务1入队
task = thread_task();
task->id = 1;
task->handler = &test1;
EnterQue(LQ, task);
//任务2入队
task = thread_task();
task->id = 2;
task->handler = &test2;
EnterQue(LQ, task);
//任务3入队
task = thread_task();
task->id = 3;
task->handler = &test3;
EnterQue(LQ, task);
cout << "队列中的元素总数:" << GetLength(LQ) << endl;
PrintQue(LQ);
//执行任务
while (task = PopQue(LQ)) {
task->handler();
cout << "删除:" << task->id <<endl;
delete[] task;
}
delete LQ;
system("pause");
return 0;
}
循环队列
在队列的顺序存储中,采用出队方式2,删除front所指的元素,然后加1并返回被删元素,这样可以避免元素移动,但是也带来一个新的问题 “假溢出” (就是前面删除的元素一直往后移动,导致队列空间越来越小),这样清空使用循环队列就可以把前面删除掉元素的空间继续存储,元素可以不断地进出;
#include <iostream>
#include <Windows.h>
using namespace std;
#define MAX_SIZE 15
//创建一个循环队列
typedef int DataType;//队列中的元素
typedef struct {
DataType que[MAX_SIZE];
DataType front;//队列头指针
DataType rear;//队列尾指针
}LinkQue;
//初始化循环队列链表
void InitQue(LinkQue* LQ) {
if (!LQ) {
return;
}
LQ->front = LQ->rear = NULL;//头尾指针同时为空
}
//判断队列是否为空
int IsEmpty(LinkQue* LQ) {
if (!LQ) {
return 0;
}
if (LQ->front == LQ->rear) {
return 1;
}
return 0;
}
//判断队列是否为满
int IsFull(LinkQue* LQ) {
if (!LQ) {
return 0;
}
if ((LQ->rear + 1) % MAX_SIZE == LQ->front) {
return 1;
}
return 0;
}
//入队
int EnterQue(LinkQue* LQ, DataType data) {
if (!LQ) {
return 0;
}
if (IsFull(LQ)) {
return 0;
}
LQ->que[LQ->rear] = data;//在队尾插入元素
LQ->rear = (LQ->rear + 1) % MAX_SIZE;//队尾指针后移一位
return 1;
}
//出队
int DeleteQue(LinkQue* LQ, DataType data) {
if (!LQ || IsEmpty(LQ) || !data) {
return 0;
}
data = LQ->que[LQ->front];//出队的元素值
LQ->front = (LQ->front + 1) % MAX_SIZE;//队首指针后移一位
return 1;
}
//打印队列中的元素
void PrintQue(LinkQue* LQ) {
if (!LQ) {
return;
}
DataType tmp;
tmp = LQ->front;
cout << "元素有:";
while (tmp!=LQ->rear) {
cout << LQ->que[tmp] << ",";
tmp = (tmp + 1) % MAX_SIZE;
}
cout << endl;
}
//获取队首元素
int GetHead(LinkQue* LQ, DataType data) {
if (!LQ || IsEmpty(LQ) || !data) {
return 0;
}
return data = LQ->que[LQ->front];
}
//获取队尾元素
int GetEnd(LinkQue* LQ, DataType data) {
if (!LQ || IsEmpty(LQ) || !data) {
return 0;
}
return data = LQ->que[LQ->rear];
}
//清空队列
void ClearQue(LinkQue* LQ) {
if (!LQ) {
return;
}
LQ->front = LQ->rear = NULL;
}
//获取队列中的元素个数
int GetLength(LinkQue* LQ) {
if (!LQ) {
return 0;
}
return (LQ->rear - LQ->front + MAX_SIZE) % MAX_SIZE;
}
优先队列
它的入队顺序没有变化,但是出队的顺序是根据优先级的高低来决定的,优先级高的限出队;
#include <iostream>
#include <Windows.h>
using namespace std;
#define MAX_SIZE 15
//创建一个链表队列
typedef int DataType;//队列中的元素
typedef struct _QNode {
int priorrty;//每个节点的优先级,按高到低
DataType data;
struct _QNode* next;
}QNode;
typedef QNode* QuePtr;
typedef struct {
int length;//队列的长度
QuePtr front;//队列头指针
QuePtr rear;//队列尾指针
}LinkQue;
//初始化循环队列链表
void InitQue(LinkQue* LQ) {
if (!LQ) {
return;
}
LQ->length = 0;
LQ->front = LQ->rear = NULL;//头尾指针同时为空
}
//判断队列是否为空
int IsEmpty(LinkQue* LQ) {
if (!LQ) {
return 0;
}
if (LQ->front == NULL) {
return 1;
}
return 0;
}
//判断队列是否为满
int IsFull(LinkQue* LQ) {
if (!LQ) {
return 0;
}
if (LQ->length==MAX_SIZE) {
return 1;
}
return 0;
}
//入队
int EnterQue(LinkQue* LQ, DataType data,int priority) {
if (!LQ||IsFull(LQ)) {
return 0;
}
QNode* qnode = new QNode;
qnode->data = data;
qnode->priorrty = priority;
qnode->next = NULL;
if (IsEmpty(LQ)) {//空队列
LQ->front = LQ->rear = qnode;
}
else {
LQ->rear->next = qnode;//在队尾插入节点
LQ->rear = qnode;//队尾指向新插入的节点
}
LQ->length++;
return 1;
}
//出队
int DeleteQue(LinkQue* LQ, DataType *data) {
if (!LQ || IsEmpty(LQ) || !data) {
return 0;
}
//保存当前已选取的最高优先级
QNode** prev_NO2 = NULL;
QNode* prev_NO1 = NULL;
//节点上一个节点的指针地址
QNode* last = NULL;
QNode* tmp = NULL;
//prev_NO2指向队首front指针的地址
prev_NO2 = &(LQ->front);
printf("第一个节点的优先级:%d\n", (*prev_NO2)->priorrty);
last = LQ->front;
tmp = last->next;
while (tmp) {
if (tmp->priorrty > (*prev_NO2)->priorrty) {
printf("抓到一个更大的优先级节点:%d\n", tmp->priorrty);
prev_NO2 = &(last->next);
prev_NO1 = last;
}
last = tmp;
tmp = tmp->next;
}
*data = (*prev_NO2)->data;
tmp = *prev_NO2;
*prev_NO2 = (*prev_NO2)->next;
delete[] tmp;
LQ->length--;
//接下来存在两种情况需要分别对待
//1.删除的是首节点,而且队列长度为0
if (LQ->length == 0) {
LQ->rear = NULL;
}
//2.删除的是尾节点
if (prev_NO1 && prev_NO1->next == NULL) {
LQ->rear = prev_NO1;
}
return 1;
}
//打印队列中的元素
void PrintQue(LinkQue* LQ) {
if (!LQ) {
return;
}
QuePtr tmp;
tmp = LQ->front;
cout << "元素有:\n";
while (tmp) {
cout << tmp->data << "优先级为:" << tmp->priorrty << "\n";
tmp = tmp->next;
}
cout << endl;
}
//获取队首元素
int GetHead(LinkQue* LQ, DataType data) {
if (!LQ || IsEmpty(LQ) || !data) {
return 0;
}
return data = LQ->front->data;
}
//获取队尾元素
int GetEnd(LinkQue* LQ, DataType data) {
if (!LQ || IsEmpty(LQ) || !data) {
return 0;
}
return data = LQ->rear->data;
}
//清空队列
void ClearQue(LinkQue* LQ) {
if (!LQ) {
return;
}
LQ->front = LQ->rear = NULL;
}
//获取队列中的元素个数
int GetLength(LinkQue* LQ) {
if (!LQ) {
return 0;
}
return LQ->length;
}
高并发 WEB 服务器队列的应用
在高并发 HTTP 反向代理服务器 Nginx 中,存在着一个跟性能息息相关的模块 - 文件缓存;经常访问到的文件会被 nginx 从磁盘缓存到内存,这样可以极大的提高 Nginx 的并发能力,不过因为 内存的限制,当缓存的文件数达到一定程度的时候就会采取淘汰机制,优先淘汰进入时间比较久或是最近 访问很少(LRU)的队列文件;
具体实现方案:
- 使用双向循环队列保存缓存的文件节点,这样可以实现多种淘汰策略: 比如:如果采用淘汰进入时间 比较久的策略,就可以使用队列的特性,先进先出 如果要采用按照 LRU,就遍历链表,找到节点删除
模拟代码实现:
#pragma once
#ifndef _NGINX_QUEUE_H
#define _NGINX_QUEUE_H
typedef struct ngx_queue_s ngx_queue_t;
//创建链式双向队列的挂件
struct ngx_queue_s {
ngx_queue_t* next;
ngx_queue_t* prev;
};
//使用宏函数实现各个功能函数
//初始化双向队列
#define ngx_queue_init(q) \
(q)->prev=q; \
(q)->next=q
//判断双向队列是否为空
#define ngx_queue_empty(h) \
(h==(h)->prev)
//根据优先级的划分:先进先出的原则
//从前面入队
#define ngx_queue_insert_hand(h,x) \
(x)->next=(h)->next; \
(x)->next->prev=x; \
(x)->prev=h; \
(h)->next=x
#define ngx_queue_insert_after ngx_queue_insert_hand
//从后面出队
#define ngx_queue_insert_tail(h,x) \
(x)->prev=(h)->next; \
(x)->prev->next=x; \
(x)->next=h; \
(h)->prev=x
//为了方便插入和删除,定义队首节点的下一个节点,和队尾的上一个节点
//双向队列头节点的下一个节点
#define ngx_queue_head(h) \
(h)->next
//双向队列尾节点的上一个节点
#define ngx_queue_last(h) \
(h)->prev
//双向队列的挂件标记
#define ngx_queue_sentinel(h) \
(h)
//新节点的下一个节点
#define ngx_queue_next(q) \
(q)->next
//新节点的上一个节
#define ngx_queue_prev(q) \
(q)->prev
//删除优先级最高的元素
#define ngx_queue_remove(x) \
(x)->next->prev=(x)->prev; \
(x)->prev->next=(x)->next
//根据地址的偏移值获取结构体中的数据
#define ngx_queue_data(q,type,link) \
(type*) ((char*)q-offsetof(type,link))
#endif // !_NGINX_QUEUE_H
#include <iostream>
#include <Windows.h>
#include <time.h>
#include "nginx_queue.h"
using namespace std;
typedef struct ngx_cached_open_file_s {
//其他属性省略...
int fd;
ngx_queue_t queue;
}ngx_cached_file_t;
typedef struct {
//其他属性省略...
ngx_queue_t expire_queue;
}ngx_open_file_cache_t;
int main(void) {
ngx_open_file_cache_t* cache = new ngx_open_file_cache_t;
ngx_queue_t* q;
ngx_queue_init(&cache->expire_queue);
//模拟文件模块,增加打开的文件到缓存中
for (int i = 0; i < 10; i++) {
cout << i << "入队" << endl;
ngx_cached_file_t* e = new ngx_cached_file_t;
e->fd = i;
ngx_queue_insert_hand(&cache->expire_queue, &e->queue);
}
//遍历队列
for (q = cache->expire_queue.next; q != ngx_queue_sentinel(&cache->expire_queue); q = q->next) {
cout << "队列中的元素:" << (ngx_queue_data(q, ngx_cached_file_t, queue))->fd << endl;
}
//模拟缓存的文件到期,执行出列操作
while (!ngx_queue_empty(&cache->expire_queue)) {
q = ngx_queue_last(&cache->expire_queue);
ngx_cached_file_t* cached_file = ngx_queue_data(q, ngx_cached_file_t, queue);
cout << "出队列的元素:" << cached_file->fd << endl;
ngx_queue_remove(q);
delete(cached_file);
}
system("pause");
return 0;
}
使用C++模板类实现循环队列,这样做可以兼容更多的数据类型
#pragma once
#ifndef QUEUE_H
#define QUEUE_H
#include <iostream>
using namespace std;
template<typename T>
class queue{
public:
queue(int size);
~queue();
bool queIsNull();//判断队列是否为空
bool queIsFull();//判断队列是否为满
int getFount();//获取队首元素
int getLength();//获取队列的元素个数
bool insert(T elem);//入队
T come();//出队
void clear();//清空队列
void pinrt();//打印队列
private:
T* data;
int size;//队列的空间
int fount;//第一个元素位置
int reat;//最后一个元素位置
};
#endif // !QUEUE_H
#include "queue.h"
template<typename T>
queue<T>::queue(int size)
{
this->size = size;
this->data = new int[size];
this->fount = 0;
this->reat = 0;
}
template<typename T>
queue<T>::~queue()
{
if (data != NULL) {
delete[] data;
data = NULL;
fount = reat = 0;
}
}
template<typename T>
bool queue<T>::queIsNull()
{
if (reat == fount) {
return true;
}
return false;
}
template<typename T>
bool queue<T>::queIsFull()
{
if (fount == (reat+1) % size) {
return true;
}
return false;
}
template<typename T>
int queue<T>::getFount()
{
return data[fount];
}
template<typename T>
int queue<T>::getLength()
{
return reat-fount;
}
template<typename T>
bool queue<T>::insert(T elem)
{
if (queIsFull()) {
return false;
}
data[reat++] = elem;
//reat++;
return true;
}
template<typename T>
T queue<T>::come()
{
if (queIsNull()||fount==reat) {
return -1;
}
T tmp;
tmp = data[fount];
fount++;
return tmp;
}
template<typename T>
void queue<T>::clear()
{
fount = 0;
reat = 0;
}
template<typename T>
void queue<T>::pinrt()
{
for (int i = fount; i < reat; i++) {
cout << data[i] << ",";
}
cout << endl;
}
注意:本人是在VS2019的编译器下实现的,如果在其他编译器有问题可以一起探讨,谢谢!!!