队列
简介
队列是一种线性表的特殊形式,特殊之处在于它只允许在表的前端(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;
}
 
执行结果
 
到此,单调队列和循环队列已经学习完毕~










![[渗透教程]-013-网络实体标识及网络监听](https://img-blog.csdnimg.cn/img_convert/a418c6f8f4a3c6eb43a0c5580d3ea7b3.jpeg)








