队列
简介
队列是一种线性表的特殊形式,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。
队列的数据元素又称为队列元素。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列具有先进先出(FIFO)的特点,可以用来实现各种数据缓存、任务队列、消息队列等应用场景。
类型
队列一般分为两种,一种是单调队列,也是日常生活中最常见的队列,无论是在食堂还是在超市都随处可见。
另一种就是循环队列,也就是队头与队尾在同一个位置(此时队列为空),存储元素时队尾向后绕圈,直到队头与队尾只相差一个距离时停止(是队尾-队头=1,而不是队头-队尾=1)。
代码实现
说了这么多,那么,如何用代码来实现队列呢?
下面将用链表的方式实现单调队列,用模拟数组的方式实现循环队列
单调队列
首先明确需要实现的几个功能:
- 初始化队列
- 判断队列是否为空(方便后续出队判断)
- 入队
- 出队
- 遍历队列元素
然后定义两个结构体,一个是节点(这里使用的是双链表的形式,方便入队和出队操作),一个是队列本体
// 结构体定义节点
typedef struct Node{
int data; //数据域
struct Node *pNext; //指针域,指向下一个节点
struct Node *pBefore; //指针域,指向上一个节点
}node,*pnode;
// 结构体定义队列
typedef struct Queue{
node *front; //队头
node *rear; //队尾
}Queue;
接着就是函数声明
//函数声明
void init(Queue *queue); //初始化队列
bool isEmpty(Queue *queue); //判断队列是否为空
bool offer(Queue *queue,int val); //数据入队
int poll(Queue *queue); //数据出队,并返回其中的数据
void traverse(Queue *queue); //遍历队列元素
然后逐个函数进行实现
初始化队列
void init(Queue *queue){
queue->front = (pnode) malloc(sizeof (node));
queue->rear = (pnode) malloc(sizeof (node));
queue->rear->pNext = queue->front;
queue->rear->pBefore = NULL;
queue->front->pBefore = queue->rear;
queue->front->pNext=NULL;
}
但看代码有点绕,但如果画个图就很简单明了了。
判断队列是否为空
bool isEmpty(Queue *queue){
if(queue->rear->pBefore==queue->front){ //如果队尾的下一个节点为队头,则证明队列为空
return true;
}
return false;
}
入队
bool offer(Queue *queue,int val){
//添加新的节点
pnode pnew = (pnode) malloc(sizeof (node));
pnew->data = val;
pnew->pNext = queue->rear->pNext;
queue->rear->pNext->pBefore = pnew;
queue->rear->pNext = pnew;
pnew->pBefore = queue->rear;
return true;
}
出队
int poll(Queue *queue){
if(isEmpty(queue)){ //如果队列为空,则无法出队
return -1;
}
int val = queue->front->pBefore->data;
pnode q = queue->front->pBefore;
queue->front->pBefore = queue->front->pBefore->pBefore;
queue->front->pBefore->pNext = queue->front;
q = NULL;
free(q);
return val;
}
遍历队列
void traverse(Queue *queue){
if(isEmpty(queue)){ //栈为空,无法遍历
return ;
}
pnode q = queue->front->pBefore;
while(q!=queue->rear){//从队头遍历到队尾,相当于从第一个人开始往后报数
printf("%d ",q->data);
q = q->pBefore;
}
printf("\n");
}
最后,在main函数中进行测试
int main(){
//定义一个队列并初始化
Queue queue;
init(&queue);
//入队操作
offer(&queue,1);
offer(&queue,2);
offer(&queue,3);
offer(&queue,4);
offer(&queue,5);
//遍历队列
traverse(&queue);
//
int num = poll(&queue);
printf("队头元素为:%d",num);
return 0;
}
运行结果
最后,附上完整的代码
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
// 结构体定义节点
typedef struct Node{
int data; //数据域
struct Node *pNext; //指针域,指向下一个节点
struct Node *pBefore; //指针域,指向上一个节点
}node,*pnode;
// 结构体定义队列
typedef struct Queue{
node *front; //队头
node *rear; //队尾
}Queue;
int main(){
//定义一个队列并初始化
Queue queue;
init(&queue);
//入队操作
offer(&queue,1);
offer(&queue,2);
offer(&queue,3);
offer(&queue,4);
offer(&queue,5);
//遍历队列
traverse(&queue);
//
int num = poll(&queue);
printf("队头元素为:%d",num);
return 0;
}
void init(Queue *queue){
queue->front = (pnode) malloc(sizeof (node));
queue->rear = (pnode) malloc(sizeof (node));
queue->rear->pNext = queue->front;
queue->rear->pBefore = NULL;
queue->front->pBefore = queue->rear;
queue->front->pNext=NULL;
}
bool isEmpty(Queue *queue){
if(queue->rear->pBefore==queue->front){ //如果队尾的下一个节点为队头,则证明队列为空
return true;
}
return false;
}
bool offer(Queue *queue,int val){
//添加新的节点
pnode pnew = (pnode) malloc(sizeof (node));
pnew->data = val;
pnew->pNext = queue->rear->pNext;
queue->rear->pNext->pBefore = pnew;
queue->rear->pNext = pnew;
pnew->pBefore = queue->rear;
return true;
}
int poll(Queue *queue){
if(isEmpty(queue)){ //如果队列为空,则无法出队
return -1;
}
int val = queue->front->pBefore->data;
pnode q = queue->front->pBefore;
queue->front->pBefore = queue->front->pBefore->pBefore;
queue->front->pBefore->pNext = queue->front;
q = NULL;
free(q);
return val;
}
void traverse(Queue *queue){
if(isEmpty(queue)){ //栈为空,无法遍历
return ;
}
pnode q = queue->front->pBefore;
while(q!=queue->rear){//从队头遍历到队尾,相当于从第一个人开始往后报数
printf("%d ",q->data);
q = q->pBefore;
}
printf("\n");
}
循环队列
根据上面的分析,这里就不过多赘述。只是循环队列比单调队列多了一个判断是否已满的功能(判断是否还能入队)
结构体定义一个队列
typedef struct Queue{ //结构体定义队列
int *pBase; // 数组模拟数据域
int front; // 队头
int rear; // 队尾
}Queue;
函数声明
void init(Queue *queue);//初始化队列
bool offer(Queue *queue,int val);//入队
void traverse(Queue *queue);//遍历并输出队列
bool isFull(Queue *queue);//判断队列是否满
bool poll(Queue *queue,int *pVal);//出队
bool isEmpty(Queue *queue);//判断队列是否空
初始化队列
void init(Queue *queue){
queue->pBase = (int *)malloc(sizeof(int)*6); // 这里只给了6个int的空间
queue->front = 0;
queue->rear = 0;
}
判断队列是否为空
bool isEmpty(Queue *queue){
if(queue->front == queue->rear){ //如果队头与队尾的下标相同则说明队列为空
return true;
}
return false;
}
判断队列是否已满
bool isFull(Queue *queue){
if((queue->rear+1)%6 == queue->front){
return true;
}
return false;
}
这里有个公式判断是否队列已满:(队尾下标+1)%队列长度==队头下标
如果该公式为true,则证明队列已满,否则为未满。
入队
bool offer(Queue *queue,int val){
if(isFull(queue)){
printf("队列已满!\n");
return false;
}
queue->pBase[queue->rear] = val;
queue->rear = (queue->rear+1)%6;
}
出队
bool poll(Queue *queue, int *pVal){
if(isEmpty(queue)){
return false;
}
*pVal = queue->pBase[queue->front];
queue->front = (queue->front+1)%6;
return true;
}
遍历队列
void traverse(Queue *queue){
int i = queue->front;
while(i!=queue->rear){
printf("%d ",queue->pBase[i]);
i = (i+1)%6;
}
printf("\n");
}
注意i的值,因为此时队头下标不一定比队尾小,所以在遍历的时候要对队列长度取模。
测试
最后,加上main主函数进行测试
int main() {
int val;
Queue queue;
//初始化队列
init(&queue);
offer(&queue,1);
offer(&queue,2);
offer(&queue,3);
offer(&queue,4);
offer(&queue,5);
offer(&queue,6);
//此时队列已满
offer(&queue,7);
offer(&queue,8);
//遍历队列
traverse(&queue);
if(poll(&queue,&val)){ //出队
printf("出队成功,队列出队的元素为%d\n",val);
}else{
printf("出队失败!\n");
}
traverse(&queue);
return 0;
}
执行结果
到此,单调队列和循环队列已经学习完毕~