数据结构与算法精讲(队列详细知识精讲)

队列

1、队列的原理精讲

队列是一种受限的线性表,(Queue),它是一种运算受限的线性表,先进先出。
其有两大特点:
1.队列是一种受限的线性结构
2.它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作。

前端删除、后端插入。
在这里插入图片描述

2、队列的算法实现

2.1、顺序存储

采用数组来保存队列的元素,设立一个队首指针front,一个队尾指针rear,分别指向队首和队尾元素。则 rear-front 即为存储的元素个数!

最初,空队列,头尾都为0处,每添加一个,尾 rear++ 往后移,这样,rear-front 就等于队列长度
在这里插入图片描述

2.1.1、初始化队列:
#define MaxSize 5 // 队列最大容量

typedef int DataType; // 队列中元素类型

typedef struct Queue {
	DataType queue[MaxSize];
	int front;  // 队头指针
	int rear;   // 队尾指针
}SeqQueue;


//队列初始化,将队列初始化为空队列
void InitQueue(SeqQueue* SQ)
{
	if (!SQ) return;
	SQ->front = SQ->rear = 0; //把对头和队尾指针同时置0
}
2.1.2、判断队列是否为空
//判断队列为空
int IsEmpty(SeqQueue* SQ)
{
	if (!SQ) return 0;
	if (SQ->front == SQ->rear)   // 头尾都在起点,为空
	{
		return 1;
	}
	return 0;
}
2.1.3、判断队列是否满了
//判断队列是否为满
int IsFull(SeqQueue* SQ)
{
	if (!SQ) return 0;
	if (SQ->rear == MaxSize)
	{
		return 1;
	}
	return 0;
}
2.1.3、打印队列
// 打印队列中的元素
void PrintQueue(SeqQueue* SQ) {
	if (!SQ) return;

	int i = SQ->front;
	while (i < SQ->rear) {
		cout <<SQ->queue[i]<<"\t";
		i++;
	}
	cout << endl;
}
2.1.4、入队

将新元素插入 rear 所指的位置,然后rear加1。

// 入队,将元素 data 插入到队列 SQ 中
int EnterQueue(SeqQueue* SQ, DataType data) {
	if (IsFull(SQ)) {
		cout << "无法插入元素: " << data << ",  因为队列满了" << endl;
		return 0;
	}

	SQ->queue[SQ->rear] = data; // 队尾插入元素
	SQ->rear++;
}
2.1.5、出队

方法一: 删除 front 所指的元素,后面所有元素前移1并返回被删除元素
在这里插入图片描述

//出队,将队列中队头的元素data出队,后面的元素向前移动
int DeleteQueue(SeqQueue* SQ, DataType* data) {
	if (!SQ || IsEmpty(SQ)) {
		cout << "队列为空!" << endl;
		return 0;
	}
	if (!data) return 0;
	*data = SQ->queue[SQ->front];
	for (int i = SQ->front + 1; i < SQ->rear; i++) {//移动后面的元素
		SQ->queue[i - 1] = SQ->queue[i];
	}
	SQ->rear--;//队尾指针前移一位
	return 1;
}

方法二: 删除front所指的元素,然后加1并返回被删元素
在这里插入图片描述

2.1.6、获取队首元素
int GetHead(SeqQueue* SQ, DataType* data)
{
	if (!SQ || IsEmpty(SQ))
	{
		cout << "队列为空!" << endl;
	}
	return *data = SQ->queue[SQ->front];
}
2.1.7、清空队列
//清空队列
void ClearQueue(SeqQueue*SQ)
 {
	 SQ->front=SQ->rear=0;
 }

2.2、顺序存储完整代码

#include<bits/stdc++.h>

using namespace std;

#define MaxSize 5 // 队列最大容量

typedef int DataType; // 队列中元素类型

typedef struct Queue {
	DataType queue[MaxSize];
	int front;  // 队头指针
	int rear;   // 队尾指针
}SeqQueue;


//队列初始化,将队列初始化为空队列
void InitQueue(SeqQueue* SQ)
{
	if (!SQ) return;
	SQ->front = SQ->rear = 0; //把对头和队尾指针同时置0
}

//判断队列为空
int IsEmpty(SeqQueue* SQ)
{
	if (!SQ) return 0;
	if (SQ->front == SQ->rear)
	{
		return 1;
	}
	return 0;
}

//判断队列是否为满
int IsFull(SeqQueue* SQ)
{
	if (!SQ) return 0;
	if (SQ->rear == MaxSize)
	{
		return 1;
	}
	return 0;
}


// 入队,将元素 data 插入到队列 SQ 中
int EnterQueue(SeqQueue* SQ, DataType data) {
	if (IsFull(SQ)) {
		cout << "无法插入元素: " << data << ",  因为队列满了" << endl;
		return 0;
	}

	SQ->queue[SQ->rear] = data; // 队尾插入元素
	SQ->rear++;
}

//出队,将队列中队头的元素data出队,后面的元素向前移动
int DeleteQueue(SeqQueue* SQ, DataType* data) {
	if (!SQ || IsEmpty(SQ)) {
		cout << "队列为空!" << endl;
		return 0;
	}
	if (!data) return 0;
	*data = SQ->queue[SQ->front];
	for (int i = SQ->front + 1; i < SQ->rear; i++) {//移动后面的元素
		SQ->queue[i - 1] = SQ->queue[i];
	}
	SQ->rear--;//队尾指针前移一位
	return 1;
}

//出队,将队列中队头的元素data出队,出队后队头指针front后移一位
int DeleteQueue2(SeqQueue* SQ, DataType* data)
{
	if (!SQ || IsEmpty(SQ))
	{
		cout << "队列为空!" << endl;
		return 0;
	}
	if (SQ->front >= MaxSize) {
		cout << "队列已到尽头!" << endl;
		return 0;
	}
	*data = SQ->queue[SQ->front]; //出队元素值
	SQ->front = (SQ->front) + 1; //队首指针后移一位
	return 1;
}

//获取队首元素
int GetHead(SeqQueue* SQ, DataType* data)
{
	if (!SQ || IsEmpty(SQ))
	{
		cout << "队列为空!" << endl;
	}
	return*data = SQ->queue[SQ->front];
}


//清空队列
void ClearQueue(SeqQueue* SQ)
{
	SQ->front = SQ->rear = 0;
}

// 打印队列中的元素
void PrintQueue(SeqQueue* SQ) {
	if (!SQ) return;

	int i = SQ->front;
	while (i < SQ->rear) {
		cout <<SQ->queue[i]<<"\t";
		i++;
	}
	cout << endl;
}

int main() {

	SeqQueue* SQ = new SeqQueue;
	DataType data;

	// 1. 初始化队列
	InitQueue(SQ);

	//2. 入队
	for (int i = 0; i < 7; i++) {
		EnterQueue(SQ, i);
	}
	PrintQueue(SQ);

	//出队
	//for(inti=0;i<10;i++){
	if (DeleteQueue2(SQ, &data)) {
		cout << "出队的元素是:" << data << endl;
	}
	else {
		cout << "出队失败!" << endl;
	}
	//}
	//打印队列中的元素
	printf("出队一个元素后,队列中剩下的元素:");
	PrintQueue(SQ);
	cout << endl;

	system("pause");
	return 0;
}

测试结果:
在这里插入图片描述

2.3、链式存储

队列的链式存储结构,其实就是线性表的单链表,只不过它只是尾进头出而已。
为了操作方便:将队头指针指向链队列的头结点,而队尾指针指向终端节点。
在这里插入图片描述
插入队列
和上文顺序存储一样,如果是第一个,则 头尾指针都指向它
如果不是第一个,则只需要尾指针指向新结点
在这里插入图片描述

//入队,将元素data 插入到队列 LQ 中
int EnterQueue(LinkQueue* LQ, DataType data) {
	if (!LQ) return 0;

	if (IsFull(LQ)) {
		cout << "队列满了,无法插入元素:" << data << endl;
		return 0;
	}
	QNode* qNode = new QNode;
	qNode->data = data;
	qNode->next = NULL;

	//判断是否空队列
	if (IsEmpty(LQ)) {
		LQ->front = LQ->rear = qNode;
	}else {
		LQ->rear->next = qNode;  // 在队尾插入结点
		LQ->rear = qNode;		//	队尾指向新的结点
	}
	LQ->length++;
}

删除队列元素
队列删除是头指针出去,所以只需要头指针指向下一个元素就好了。
在这里插入图片描述

//出队,将队列中队头的元素出队,其后的第一个元素成为新的队首
int DeleteQueue(LinkQueue* LQ, DataType* data) {
	if (!LQ || IsEmpty(LQ) ){
		cout<<"队列为空"<<endl;
		return 0;
	}
	QNode* temp = NULL;
	temp = LQ->front;
	LQ->front = LQ->front->next;
	if (!temp->next) {
		LQ->rear = NULL; // 相当于对头出列后就没东西了,此时队尾也要置为空
	}

	*data = temp->data; 
	LQ->length--;

	delete temp;
	return 1;
}

2.4、链式存储完整代码

#include<bits/stdc++.h>

using namespace std;

#define Maxsize 5		// 队列最大容量

typedef int DataType; //队列中元素类型

typedef struct _QNode{ //结点结构
	DataType data;
	struct _QNode* next;
}QNode;

typedef QNode * QueuePtr;

typedef struct Queue
{
	int length; //队列的长度
	QueuePtr front; //队头指针
	QueuePtr rear; //队尾指针
}LinkQueue;

// 队列初始化,将队列初始化为空队列
void InitQueue(LinkQueue* LQ) {
	if (!LQ) return;

	LQ->length = 0;   // 长度为0
	LQ->front = LQ->rear = NULL;  // 队头队尾指针都指向 0
} 


// 判断队列为空
int IsEmpty(LinkQueue* LQ) {
	if (!LQ) return 0;
	if (LQ->rear == NULL) {
		return 1;
	}
	return 0;
}

//判断队列是否为满
int IsFull(LinkQueue* LQ) {
	if (!LQ) return 0;
	if (LQ->length == Maxsize) {
		cout << "队列满了" << endl;
		return 1;
	}
	return 0;
}


//入队,将元素data 插入到队列 LQ 中
int EnterQueue(LinkQueue* LQ, DataType data) {
	if (!LQ) return 0;

	if (IsFull(LQ)) {
		cout << "队列满了,无法插入元素:" << data << endl;
		return 0;
	}
	QNode* qNode = new QNode;
	qNode->data = data;
	qNode->next = NULL;

	//判断是否空队列
	if (IsEmpty(LQ)) {
		LQ->front = LQ->rear = qNode;
	}else {
		LQ->rear->next = qNode;  // 在队尾插入结点
		LQ->rear = qNode;		//	队尾指向新的结点
	}
	LQ->length++;
}

//出队,将队列中队头的元素出队,其后的第一个元素成为新的队首
int DeleteQueue(LinkQueue* LQ, DataType* data) {
	if (!LQ || IsEmpty(LQ) ){
		cout<<"队列为空"<<endl;
		return 0;
	}
	QNode* temp = NULL;
	temp = LQ->front;
	LQ->front = LQ->front->next;
	if (!temp->next) {
		LQ->rear = NULL; // 相当于对头出列后就没东西了,此时队尾也要置为空
	}

	*data = temp->data; 
	LQ->length--;

	delete temp;
	return 1;
}

// 打印队列中的元素
void PrintQueue(LinkQueue* LQ) {
	QNode* temp = NULL;

	if (!LQ) return;

	temp = LQ->front;
	while (temp) {
		cout << setw(4) << temp->data;
		temp = temp->next;
	}
	cout << endl;
}

// 获取队首元素,不出队
int GetHead(LinkQueue* LQ, DataType* data) {
	if (!LQ || IsEmpty(LQ)) {
		cout << "队列为空" << endl;
		return 0;
	}
	*data = LQ->front->data;
	return 1;
}

//队列清空
void ClearQueue(LinkQueue* LQ) {
	if (!LQ) return;

	while (LQ->front) {
		QNode* temp = LQ->front->next;
		delete LQ->front;
		LQ->front = temp;
	}

	LQ->front = LQ->rear = NULL;
	LQ->length = 0;
}

// 获取队列中的元素的个数
int getLength(LinkQueue* LQ) {
	if (!LQ) return 0;
	//int index = 0;
	//while (LQ->front) {
	//	index++;
	//	LQ->front = LQ->front->next;
	//}
	//return index; 

	return LQ->length;
}

int main() {
	LinkQueue* LQ = new LinkQueue;
	DataType data = -1;

	// 初始化队列
	InitQueue(LQ);

	// 入队
	for (int i = 0; i < 7; i++) {
		EnterQueue(LQ, i);
	}
	//打印队列元素
	printf("队列中的元素(总共%d个):", getLength(LQ));
	PrintQueue(LQ);
	cout << endl;
	//出队
   //for(inti=0;i<10;i++){
	if (DeleteQueue(LQ, &data)) {
		cout << "出队的元素是:" << data << endl;
	}
	else {
		cout << "出队失败!" << endl;
	}
	//}
	//打印队列中的元素
	printf("出队一个元素后,队列中剩下的元素[%d]:", getLength(LQ));
	PrintQueue(LQ);
	cout << endl;
	cout << "队列长度" << getLength(LQ) << endl;
	ClearQueue(LQ);
	cout << "清空队列!\n";
	PrintQueue(LQ);
	//清理资源
	delete LQ;

	system("pause");
	return 0;
}

测试结果:
在这里插入图片描述

3、线程池中的任务队列

线程池-由一个任务队列和一组处理队列的线程组成。 一旦工作进程需要处理某个可能“阻塞”的操作,不用自己操作,将其作为一个任务放到线程池的队列,接着会被某个空闲线程提取处理。
在这里插入图片描述
完整代码如下:
代码中,我是用了 calloc,再次对比 colloc 和 malloc 区别
前者是分配内存后还进行了初始化, 内存值为 0
后置是随机

#include<bits/stdc++.h>
#include<Windows.h>
#include<iomanip>
#include<assert.h>

using namespace std;

#define MaxSize 100		// 队列最大容量

typedef int DataType; //队列中元素类型

typedef struct _QNode{ //结点结构
	int id;
	void (*handler)(void);
	struct _QNode* next;
}QNode;

typedef QNode * QueuePtr;

typedef struct Queue
{
	int length; //队列的长度
	QueuePtr front; //队头指针
	QueuePtr rear; //队尾指针
}LinkQueue;

// 队列初始化,将队列初始化为空队列
void InitQueue(LinkQueue* LQ) {
	if (!LQ) return;

	LQ->length = 0;   // 长度为0
	LQ->front = LQ->rear = NULL;  // 队头队尾指针都指向 0
} 


//分配线程执行的任务节点
QueuePtr thread_task_alloc()
{
	QNode* task;
	task = (QNode*)calloc(1, sizeof(QNode));  // 分配内存空间
	if (task == NULL) {
		return NULL;
	}
	return task;
}

//判断队列为空
int IsEmpty(LinkQueue* LQ)
{
	if (!LQ) return 0;
	if (LQ->front == NULL)
	{
		return 1;
	}
	return 0;
}
//判断队列是否为满
int IsFull(LinkQueue* LQ)
{
	if (!LQ) return 0;
	if (LQ->length == MaxSize)
	{
		return 1;
	}
	return 0;
}

//入队,将元素data插入到队列LQ中
int EnterQueue(LinkQueue* LQ, QNode* node) {
	if (!LQ || !node) return 0;

	if (IsFull(LQ)) {
		cout << "无法插入任务" << node->id << ",队列已满!" << endl;
		return 0;
	}

	node->next = NULL;
	if (IsEmpty(LQ)) {//空队列
		LQ->front = LQ->rear = node;
	}
	else {
		LQ->rear->next = node;	//在队尾插入节点qNode
		LQ->rear = node;		//队尾指向新插入的节点
	}
	LQ->length++;
	return 1;
}

//出队,将队列中队头的节点出队,返回头节点
QNode* PopQueue(LinkQueue* LQ) {
	if (!LQ || IsEmpty(LQ)) {
		cout << "队列为空" << endl;
		return 0;
	}
	QNode* temp = NULL;
	temp = LQ->front;
	LQ->front = LQ->front->next;

	if (!LQ->front) {
		LQ->rear = NULL;  // 出头后队列为空,队尾也置为空
	}
	LQ->length--;
	return temp;
}


//打印队列中的各元素
void PrintQueue(LinkQueue* LQ)
{
	QueuePtr tmp;
	if (!LQ)return;

	if (LQ->front == NULL) {
		cout << "队列为空!";
		return;
	}

	tmp = LQ->front;
	while (tmp)
	{
		cout << setw(4) << tmp->id;
		tmp = tmp->next;
	}
	cout << endl;
}

int getLength(LinkQueue* LQ) {
	if (!LQ) {
		cout << "队列为空!";
		return 0;
	}
	return LQ->length;
}

void task1() {
	printf("我是任务1...\n");
}
void task2() {
	printf("我是任务2...\n");
}

int main() {
	LinkQueue* LQ = new LinkQueue;
	QNode* task = NULL;

	//初始化队列
	InitQueue(LQ);

	//任务1入队
	task = thread_task_alloc();
	task->id = 1;
	task->handler = &task1;
	EnterQueue(LQ, task);

	//任务2入队
	task = thread_task_alloc();
	task->id = 2;
	task->handler = &task2;
	EnterQueue(LQ, task);

	//打印任务队列中的元素
	printf("队列中的元素(总共%d个):", getLength(LQ));
	PrintQueue(LQ);
	cout << endl;

	//执行任务
	while ((task = PopQueue(LQ))) {
		task->handler();
		delete task;
	}
	//清理资源
	delete LQ;

	system("pause");
	return 0;
}

测试结果:
在这里插入图片描述

4、循环队列

比如我们在队列的顺序存储中,如果采用出队方式 2, 删除 front 所指的元素,然后加 1 并返回被删元素。这样可以避免元素移动,但是也带来了一个新的问题“假溢出”。
很多空间都浪费了。
在这里插入图片描述
可以考虑循环队列,将前面的空间重复利用:
流出一个空间,这样更好的判断,是否队满了。
在这里插入图片描述
循环队列入队, 队尾循环后移:

SQ->rear =(SQ->rear+1%Maxsize;

循环队列出队, 队首循环后移:

 SQ->front =(SQ->front+1%Maxsize;

队空:

SQ.front=SQ.rear; // SQ.rear 和 SQ.front 指向同一个位置

队满:

 (SQ.rear+1) %Maxsize=SQ.front; // SQ.rear 向后移一位正好是SQ.front

在这里插入图片描述
计算元素个数
如果 SQ.rear>= SQ.front:元素个数为 SQ.rear-SQ.front;
如果 SQ.rear<SQ.front:元素个数为SQ.rear-SQ.front+Maxsize
可以统一起来:

(SQ.rear-SQ.front+Maxsize)%Maxsize;

完整代码如下:

#include <stdio.h>
#include <assert.h>
#include <Windows.h>
#include <iostream>
#include <iomanip>
using namespace std;

#define MaxSize 5 //循环队列的最大容量

typedef int DataType; //循环队列中元素类型

typedef struct Queue
{
	DataType queue[MaxSize];
	int front; //循环队头指针
	int rear; //循环队尾指针
}SeqQueue;

//队列初始化,将循环队列初始化为空队列
void InitQueue(SeqQueue* SQ)
{
	if (!SQ) return;
	SQ->front = SQ->rear = 0; //把对头和队尾指针同时置 0
}

//判断队列为空
int IsEmpty(SeqQueue* SQ)
{
	if (!SQ) return 0;
	if (SQ->front == SQ->rear)
	{
		return 1;
	}
	return 0;
}

//判断循环队列是否为满
int IsFull(SeqQueue*SQ)
{
	if (!SQ) return 0;
	if ((SQ->rear + 1) % MaxSize == SQ->front)
	{
		return 1;
	}
	return 0;
}

//入队,将元素 data 插入到循环队列 SQ 中
int EnterQueue(SeqQueue* SQ, DataType data) {
	if (!SQ) return 0;
	if (IsFull(SQ)) {
		cout << "无法插入元素 " << data << ", 队列已满!" << endl;
		return 0;
	}
	SQ->queue[SQ->rear] = data; //在队尾插入元素 data
	SQ->rear = (SQ->rear + 1) % MaxSize; //队尾指针循环后移一位
	return 1;
}

//出队,将队列中队头的元素 data 出队,出队后队头指针 front 后移一位
int DeleteQueue(SeqQueue* SQ, DataType* data)
{
	if (!SQ || IsEmpty(SQ))
	{
		cout << "循环队列为空!" << endl;
		return 0;
	}
	*data = SQ->queue[SQ->front]; //出队元素值
	SQ->front = (SQ->front + 1) % MaxSize; //队首指针后移一位
	return 1;
}
//打印队列中的各元素
void PrintQueue(SeqQueue* SQ)
{
	if (!SQ) return;
	int i = SQ->front;
	while (i != SQ->rear) {
		cout << setw(4) << SQ->queue[i];
		i = (i + 1) % MaxSize;
	}
	cout << endl;
}
//获取队首元素,不出队
int GetHead(SeqQueue* SQ, DataType* data)
{
	if (!SQ || IsEmpty(SQ))
	{
		cout << "队列为空!" << endl;
	}
	return *data = SQ->queue[SQ->front];
}
//清空队列
void ClearQueue(SeqQueue* SQ)
{
	if (!SQ) return;
	SQ->front = SQ->rear = 0;
}
//获取队列中元素的个数
int getLength(SeqQueue* SQ) {
	if (!SQ) return 0;
	return (SQ->rear - SQ->front + MaxSize) % MaxSize;
}
int main()
{
	SeqQueue* SQ = new SeqQueue;
	DataType data = -1;
	//初始化队列
	InitQueue(SQ);
	//入队
	for (int i = 0; i < 7; i++) {
		EnterQueue(SQ, i);
	}
	//打印队列中的元素
	printf("队列中的元素(总共%d 个):", getLength(SQ));
	PrintQueue(SQ); cout << endl;
	//出队
	for (int i = 0; i < 5; i++) {
		if (DeleteQueue(SQ, &data)) {
			cout << "出队的元素是:" << data << endl;
		}
		else {
			cout << "出队失败!" << endl;
		}
	}
	//打印队列中的元素
	printf("出队五个元素后,队列中剩下的元素个数为 %d 个:",
		getLength(SQ));
	PrintQueue(SQ);
	cout << endl;
	//入队 4 个
	for (int i = 0; i < 4; i++) {
		EnterQueue(SQ, i + 10);
	}
	printf("\n 入队四个元素后,队列中剩下的元素个数为 %d 个:",
		getLength(SQ));
	PrintQueue(SQ);
	system("pause");
	return 0;
}

测试结果:
在这里插入图片描述

5、优先队列

优先队列 : 它的入队顺序没有变化,但是出队的顺序是根据优先级的高低来决定的。优先级高的优先出队。
在这里插入图片描述
因为存在优先级问题,所以定义结构体,就得多定义个优先级。
结构体定义和初始化:

#define MaxSize 5 //队列的最大容量

typedef int DataType; //任务队列中元素类型
typedef struct _QNode { //结点结构
	int priority; //每个节点的优先级,0 最低优先级,9 最高优先级,优先级相同,取第一个节点
	DataType data;
	struct _QNode* next;
}QNode;

typedef QNode* QueuePtr;

typedef struct Queue
{
	int length; //队列的长度
	QueuePtr front; //队头指针
	QueuePtr rear; //队尾指针
}LinkQueue;

//队列初始化,将队列初始化为空队列
void InitQueue(LinkQueue* LQ)
{
	if (!LQ) return;
	LQ->length = 0;
	LQ->front = LQ->rear = NULL; //把对头和队尾指针同时置 0
}

空的队列和之前没什么变化,当队头为空就属于空。
插入元素和之前也没有变化,队尾插入。
在这里插入图片描述
插入和判断是否为空,是否已经队满如下:

//判断队列为空
int IsEmpty(LinkQueue* LQ)
{
	if (!LQ) return 0; 
	if (LQ->front == NULL)
	{
		return 1;
	}
	return 0;
}

//判断队列是否为满
int IsFull(LinkQueue* LQ)
{
	if (!LQ) return 0;
	if (LQ->length == MaxSize)
	{
		return 1;
	}
	return 0;
}

//入队,将元素 data 插入到队列 LQ 中
int EnterQueue(LinkQueue* LQ, DataType data, int priority) {
	if (!LQ) return 0;
	if (IsFull(LQ)) {
		cout << "无法插入元素 " << data << ", 队列已满!" << endl;
		return 0;
	}
	QNode* qNode = new QNode;
	qNode->data = data;
	qNode->priority = priority;
	qNode->next = NULL;
	
	if (IsEmpty(LQ)) {//空队列
		LQ->front = LQ->rear = qNode;
	}
	else {
		LQ->rear->next = qNode;//在队尾插入节点 qNode
		LQ->rear = qNode;    //队尾指向新插入的节点
	}
	LQ->length++;
	return 1;
}

删除元素:
因为我们存在优先级问题,就是谁的优先级高,我删除谁。
所以首先得遍历找出最高优先级,如果存在相同优先级,谁在前面删除谁。
在这里插入图片描述

//出队,遍历队列,找到队列中优先级最高的元素 data 出队
int DeleteQueue(LinkQueue* LQ, DataType* data) {
	QNode** prev = NULL, * prev_node = NULL;//保存当前已选举的最高优先级节点上一个节点的指针地址。
	QNode * last = NULL, *tmp = NULL;
	if (!LQ || IsEmpty(LQ)) {
		cout << "队列为空!" << endl;
		return 0;
	}
	if (!data) return 0;
	//prev 指向队头 front 指针的地址
	prev = &(LQ->front);  // 这个地址指向的是第一个节点
	printf("第一个节点的优先级: %d\n", (*prev)->priority);
	last = LQ->front;
	tmp = last->next;
	while (tmp) {
		if (tmp->priority > (*prev)->priority) {
			printf("抓到个更大优先级的节点[priority: %d]\n",
			tmp->priority);
			prev = &(last->next);
			prev_node = last;
		}
		last = tmp;
		tmp = tmp->next;
	}
	*data = (*prev)->data;
	tmp = *prev;
	*prev = (*prev)->next; // 这一步就是属于跳过最优先那个
	delete tmp;
	LQ->length--;

	//接下来存在 2 种情况需要分别对待
	//1.删除的是首节点,而且队列长度为零
	if (LQ->length == 0) {
		LQ->rear = NULL;
	}
	//2.删除的是尾部节点
	if (prev_node && prev_node->next == NULL) {
		LQ->rear = prev_node;
	}
	return 1;
}

特别搞明白 prev 是代表什么。
在这里插入图片描述
完整代码:

#include <stdio.h>
#include <assert.h>
#include <Windows.h>
#include <iostream>
#include <iomanip>
using namespace std;

#define MaxSize 5 //队列的最大容量

typedef int DataType; //任务队列中元素类型
typedef struct _QNode { //结点结构
	int priority; //每个节点的优先级,0 最低优先级,9 最高优先级,优先级相同,取第一个节点
		DataType data;
	struct _QNode* next;
}QNode;

typedef QNode* QueuePtr;

typedef struct Queue
{
	int length; //队列的长度
	QueuePtr front; //队头指针
	QueuePtr rear; //队尾指针
}LinkQueue;

//队列初始化,将队列初始化为空队列
void InitQueue(LinkQueue* LQ)
{
	if (!LQ) return;
	LQ->length = 0;
	LQ->front = LQ->rear = NULL; //把对头和队尾指针同时置 0
}

//判断队列为空
int IsEmpty(LinkQueue* LQ)
{
	if (!LQ) return 0; if (LQ->front == NULL)
	{
		return 1;
	}
	return 0;
}

//判断队列是否为满
int IsFull(LinkQueue* LQ)
{
	if (!LQ) return 0;
	if (LQ->length == MaxSize)
	{
		return 1;
	}
	return 0;
}
//入队,将元素 data 插入到队列 LQ 中
int EnterQueue(LinkQueue* LQ, DataType data, int priority) {
	if (!LQ) return 0;
	if (IsFull(LQ)) {
		cout << "无法插入元素 " << data << ", 队列已满!" << endl;
		return 0;
	}
	QNode* qNode = new QNode;
	qNode->data = data;
	qNode->priority = priority;
	qNode->next = NULL;
	if (IsEmpty(LQ)) {//空队列
		LQ->front = LQ->rear = qNode;
	}
	else {
		LQ->rear->next = qNode;//在队尾插入节点 qNode
		LQ->rear = qNode; //队尾指向新插入的节点
	}
	LQ->length++;
	return 1;
}

//出队,遍历队列,找到队列中优先级最高的元素 data 出队
int DeleteQueue(LinkQueue* LQ, DataType* data) {
	QNode** prev = NULL, * prev_node = NULL;//保存当前已选举的最高优先级节点上一个节点的指针地址。
	QNode * last = NULL, *tmp = NULL;
	if (!LQ || IsEmpty(LQ)) {
		cout << "队列为空!" << endl;
		return 0;
	}
	if (!data) return 0;

	//prev 指向队头 front 指针的地址
	prev = &(LQ->front);
	printf("第一个节点的优先级: %d\n", (*prev)->priority);
	last = LQ->front;
	tmp = last->next;
	while (tmp) {
		if (tmp->priority > (*prev)->priority) {
			printf("抓到个更大优先级的节点[priority: %d]\n",tmp->priority);
			prev = &(last->next);
			prev_node = last;
		}
		last = tmp;
		tmp = tmp->next;
	}
	*data = (*prev)->data;
	tmp = *prev;
	*prev = (*prev)->next;  // 这一步就是属于跳过最优先那个
	delete tmp;
	LQ->length--;

	//接下来存在 2 种情况需要分别对待
	//1.删除的是首节点,而且队列长度为零
	if (LQ->length == 0) {
		LQ->rear = NULL;
	}
	//2.删除的是尾部节点
	if (prev_node && prev_node->next == NULL) {
		LQ->rear = prev_node;
	}
	return 1;
}

//打印队列中的各元素
void PrintQueue(LinkQueue* LQ)
{
	QueuePtr tmp;
	if (!LQ) return;
	if (LQ->front == NULL) {
		cout << "队列为空!";
		return;
	}
	tmp = LQ->front;
	while (tmp)
	{
		cout << setw(4) << tmp->data << "[" << tmp->priority << "]";
		tmp = tmp->next;
	}
	cout << endl;
}

//获取队首元素,不出队
int GetHead(LinkQueue* LQ, DataType* data)
{
	if (!LQ || IsEmpty(LQ))
	{
		cout << "队列为空!" << endl;
		return 0;
	}
	if (!data) return 0;
	*data = LQ->front->data;
	return 1;
}

//清空队列
void ClearQueue(LinkQueue* LQ)
{
	if (!LQ) return;
	while (LQ->front) {
		QueuePtr tmp = LQ->front->next;
		delete LQ->front;
		LQ->front = tmp;
	}
	LQ->front = LQ->rear = NULL; 
	LQ->length = 0;
}

//获取队列中元素的个数
int getLength(LinkQueue* LQ) {
	if (!LQ) return 0;
	return LQ->length;
}
int main()
{
	LinkQueue* LQ = new LinkQueue;
	DataType data = -1;
	//初始化队列
	InitQueue(LQ);
	//入队
	for (int i = 0; i < 5; i++) {
		EnterQueue(LQ, i + 10, i);
	}
	//打印队列中的元素
	printf("队列中的元素(总共%d 个):", getLength(LQ));
	PrintQueue(LQ);
	cout << endl;
	//出队
	for (int i = 0; i < 5; i++) {
		if (DeleteQueue(LQ, &data)) {
			cout << "出队的元素是:" << data << endl;
		}
		else {
			cout << "出队失败!" << endl;
		}
	}
	//打印队列中的元素
	printf("出队五个元素后,队列中剩下的元素[%d]:\n", getLength(LQ));
	PrintQueue(LQ);
	cout << endl;
	ClearQueue(LQ);
	cout << "清空队列!\n"; PrintQueue(LQ);

	//清理资源
	delete LQ;
	system("pause");
	return 0;
}

测试结果:
在这里插入图片描述

6、高并发WEB服务器队列的应用

在高并发 HTTP 反向代理服务器 Nginx 中,存在着一个跟性能息息相关的模块 - 文件缓存。
在这里插入图片描述
经常访问到的文件会被 nginx 从磁盘缓存到内存,这样可以极大的提高 Nginx 的并发能力,不过因为
内存的限制,当缓存的文件数达到一定程度的时候就会采取淘汰机制,优先淘汰进入时间比较久或是最近访问很少(LRU)的队列文件。

设计方案:
使用双向循环队列保存缓存的文件节点,这样可以实现多种淘汰策略
比如:如果采用淘汰进入时间比较久的策略,就可以使用队列的特性,先进先出如果要采用按照LRU,就遍历链表,找到节点删除。

// nginx_queue.h

#ifndef _NGX_QUEUE_H_INCLUDED_
#define _NGX_QUEUE_H_INCLUDED_

typedef struct ngx_queue_s ngx_queue_t;

struct ngx_queue_s {
	ngx_queue_t* prev;
	ngx_queue_t* next;
};
#define ngx_queue_init(q) \
	(q)->prev = q; \
	(q)->next = q
#define ngx_queue_empty(h) \
	(h == (h)->prev)
#define ngx_queue_insert_head(h, x) \
	(x)->next = (h)->next; \
	(x)->next->prev = x; \
	(x)->prev = h; \
	(h)->next = x
#define ngx_queue_insert_after ngx_queue_insert_head
#define ngx_queue_insert_tail(h, x) \
	(x)->prev = (h)->prev; \
	(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.cpp

#include <Windows.h>
#include <stdlib.h>
#include <iostream>
#include "nginx_queue.h"
#include <time.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);

	//1. 模拟文件模块,增加打开的文件到缓存中
	for (int i = 0; i < 10; i++) {
		ngx_cached_file_t* e = new ngx_cached_file_t;
		e->fd = i;
		ngx_queue_insert_head(&cache->expire_queue, &e->queue);
	}

	//遍历队列
	for (q = cache->expire_queue.next;
		q != ngx_queue_sentinel(&cache->expire_queue); q = q->next) {
		printf("队列中的元素:%d\n", (ngx_queue_data(q,
			ngx_cached_file_t, queue))->fd);
	}

	//模拟缓存的文件到期,执行出列操作
	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);
		printf("出队列中的元素:%d\n", cached_file->fd);
		ngx_queue_remove(q);
		delete(cached_file);
	}
	system("pause");
	return 0;
}

测试结果:
在这里插入图片描述

  • 14
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值