【数据结构】队列“0”基础知识讲解 + 实战演练
一、概念与结构
概念:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)
入队列:进行插入操作的一端称为队尾
出队列:进行删除操作的一端称为队头

队列底层结构选型
队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。
我们选择链表而不选择数组有如下几点优势:
1. 内存动态分配与灵活扩展性
- 链表的动态特性:链表在内存中是通过节点的指针连接起来的,每个节点都是在需要时通过动态内存分配(如 C 语言中的
malloc)获得。这意味着链表的长度可以根据实际需求随时增加或减少,无需预先指定固定的大小。例如,在处理一个元素数量不确定的任务队列时,随着任务的不断加入和完成,链表能够轻松适应元素数量的变化,不会出现像数组那样因空间不足而需要重新分配内存并复制数据的情况。- 数组的固定空间限制:数组在定义时需要指定大小,一旦确定,其占用的内存空间就固定下来。如果事先分配的空间过大,会造成内存浪费;若分配空间过小,当队列元素数量超过数组容量时,就需要重新分配更大的数组,并将原数组中的元素复制到新数组中,这会增加时间和空间开销。
2. 插入和删除操作效率
- 链表的高效插入删除:在链表中进行插入和删除操作时,只需修改相关节点的指针指向即可。对于队列的入队操作(在队尾插入元素)和出队操作(在队头删除元素),时间复杂度均为 O(1) 。以单链表实现队列为例,入队时创建新节点并将其链接到队尾,出队时将头节点指向下一个节点并释放原头节点。
- 数组的插入删除开销:在数组中进行插入和删除操作,尤其是在数组头部或中间位置时,需要移动大量元素来保持数组的连续性。对于队列的出队操作(删除队头元素),后续元素都需要向前移动,时间复杂度为 O(n),其中 n 是队列中元素的个数。这在元素数量较多时,会显著影响操作效率。
3. 内存碎片化影响
- 链表受碎片化影响小:链表的节点在内存中是分散存储的,即使内存中存在一些碎片化的空闲空间,也能利用这些空间来分配新的节点。只要系统还有可用内存,就能继续创建节点,不会因为内存碎片化导致无法为队列分配空间。
- 数组受碎片化影响大:数组要求连续的内存空间,当内存出现碎片化时,即使总的空闲内存足够,但如果没有一块连续的足够大的空间,就无法为数组扩容,从而可能导致队列无法正常工作。
4. 内存使用的针对性
- 链表按需分配:链表可以根据实际存储的元素数量来分配内存,每个节点只存储数据和指针,对于元素数量较少的队列,不会占用过多的额外空间。
- 数组的预分配浪费:数组不管实际存储多少元素,都需要预先分配固定大小的空间,当队列中元素数量远小于数组大小时,会造成内存的浪费。
不过,链表也并非完全没有缺点,比如它相较于数组,访问元素时只能顺序遍历,无法像数组那样通过下标直接随机访问,而且每个节点需要额外存储指针,会占用一定的内存空间。
二、队列的实现(重点)
2.1、Queue.h
#define _CRT_SECURE_NO_WARNINGS
#pragma once#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>typedef int QDataType;
//定义队列结点结构
typedef struct QueueNode
{int data;struct QueueNode* next;
}QNode;//定义队列结构
typedef struct Queue
{QNode* phead;//指向队头结点的指针QNode* ptail;//指向队尾结点的指针//int size;//队列中有效数据个数
}Queue;//初始化队列
void QueueInit(Queue* pq);//销毁队列
void QueueDestroy(Queue* pq);// ⼊队列,队尾
void QueuePush(Queue* pq, QDataType x);//队列判空
bool QueueEmpty(Queue* pq);// 出队列,队头
void QueuePop(Queue* pq);//取队头数据
QDataType QueueFront(Queue* pq);//取队尾数据
QDataType QueueBack(Queue* pq);//队列有效元素个数
int QueueSize(Queue* pq);
2.2、Queue.c
#include"Queue.h"//初始化队列
void QueueInit(Queue* pq)
{assert(pq);pq->phead = pq->ptail = NULL;
}//销毁队列
void QueueDestroy(Queue* pq)
{assert(pq);QNode* pcur = pq->phead;while (pcur){Queue* next = pcur->next;free(pcur);pcur = next;}//队列销毁完成pq->phead = pq->ptail = NULL;
}// ⼊队列,队尾
void QueuePush(Queue* pq, QDataType x)
{assert(pq);//创建值为x的结点QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL){perror("malloc fail!");exit(1);}newnode->data = x;newnode->next = NULL;//队列为空if (pq->phead == NULL){pq->phead = pq->ptail = newnode;}else{pq->ptail->next = newnode;pq->ptail = pq->ptail->next;}
}//队列判空
bool QueueEmpty(Queue* pq)
{assert(pq);return pq->phead == NULL;
}// 出队列,队头
void QueuePop(Queue* pq)
{assert(!QueueEmpty(pq));//队列中只有一个结点if (pq->phead == pq->ptail){free(pq->phead);pq->phead = pq->ptail = NULL;}else{Queue* next = pq->phead->next;free(pq->phead);pq->phead = next;}
}//取队头数据
QDataType QueueFront(Queue* pq)
{assert(!QueueEmpty(pq));return pq->phead->data;
}//取队尾数据
QDataType QueueBack(Queue* pq)
{assert(!QueueEmpty(pq));return pq->ptail->data;
}//队列有效元素个数
int QueueSize(Queue* pq)
{assert(pq);QNode* pcur = pq->phead;int size = 0;while (pcur){size++;pcur = pcur->next;}return size;
}
2.3、test.c
#include"Queue.h"void test01()
{Queue q;QueueInit(&q);QueuePush(&q, 1);QueuePush(&q, 2);QueuePush(&q, 3);QueuePush(&q, 4);/*QueuePop(&q);QueuePop(&q);QueuePop(&q);QueuePop(&q);*/printf("对头: %d\n", QueueFront(&q));printf("队尾: %d\n", QueueBack(&q));printf("size: %d\n", QueueSize(&q));QueueDestroy(&q);
}int main()
{test01();return 0;
}
三、队列算法题(难点)
3.1、用队列实战栈
https://leetcode.cn/problems/implement-stack-using-queues/description/
思路:俩个队列配合使用

typedef int QDataType;
//定义队列结点结构
typedef struct QueueNode {int data;struct QueueNode* next;
}QueueNode;//定义队列结构
typedef struct Queue {QueueNode* phead;//指向对头结点的指针QueueNode* ptail;//指向队尾结点的指针//int size;//队列中有效数据个数
}Queue;//初始化队列
void QueueInit(Queue* pq)
{assert(pq);pq->phead = pq->ptail = NULL;
}//销毁队列
void QueueDestroy(Queue* pq)
{assert(pq);QueueNode* pcur = pq->phead;while (pcur){struct QueueNode* next = pcur->next;free(pcur);pcur = next;}//队列销毁完成pq->phead = pq->ptail = NULL;
}// ⼊队列,队尾
void QueuePush(Queue* pq, QDataType x)
{assert(pq);//创建值为x的结点QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));if (newnode == NULL){perror("malloc fail!");exit(1);}newnode->data = x;newnode->next = NULL;//队列为空if (pq->phead == NULL){pq->phead = pq->ptail = newnode;}else{pq->ptail->next = newnode;pq->ptail = pq->ptail->next;}
}//队列判空
bool QueueEmpty(Queue* pq)
{assert(pq);return pq->phead == NULL;
}// 出队列,队头
void QueuePop(Queue* pq)
{assert(!QueueEmpty(pq));//队列中只有一个结点if (pq->phead == pq->ptail){free(pq->phead);pq->phead = pq->ptail = NULL;}else{struct QueueNode* next = pq->phead->next;free(pq->phead);pq->phead = next;}
}//取队头数据
QDataType QueueFront(Queue* pq)
{assert(!QueueEmpty(pq));return pq->phead->data;
}//取队尾数据
QDataType QueueBack(Queue* pq)
{assert(!QueueEmpty(pq));return pq->ptail->data;
}//队列有效元素个数
int QueueSize(Queue* pq)
{assert(pq);QueueNode* pcur = pq->phead;int size = 0;while (pcur){size++;pcur = pcur->next;}return size;
}//-------以上是队列的结构和常用方法-------//俩个队列实现一个栈
typedef struct {Queue q1;Queue q2;
} MyStack;//创建一个栈
MyStack* myStackCreate() {MyStack* pst = (MyStack*)malloc(sizeof(MyStack));QueueInit(&pst->q1);QueueInit(&pst->q2);return pst;
}void myStackPush(MyStack* obj, int x) {//往不为空的队列中插入数据if(!QueueEmpty(&obj->q1)){//q1QueuePush(&obj->q1,x);}else{//q2QueuePush(&obj->q2,x);}
}int myStackPop(MyStack* obj) {//找不为空队列Queue* emp = &obj->q1;Queue*noneEmp = &obj->q2;if((QueueEmpty(&obj->q2))){emp = &obj->q2;noneEmp = &obj->q1;}//不为空队列前size-1个数据挪到空队列中while(QueueSize(noneEmp) > 1){//取队头int front = QueueFront(noneEmp);//入另一个队列QueuePush(emp,front);//出队头QueuePop(noneEmp);}//不为空队列数据出队int top = QueueFront(noneEmp); QueuePop(noneEmp); return top;}//取栈顶
int myStackTop(MyStack* obj) {//找不为空队列,返回不为空队列的队尾数据 if(!QueueEmpty(&obj->q1)){return QueueBack(&obj->q1);}else{return QueueBack(&obj->q2);}
}bool myStackEmpty(MyStack* obj) {return QueueEmpty(&obj->q1) && QueueEmpty(&obj->q2);
}void myStackFree(MyStack* obj) {QueueDestroy(&obj->q1);QueueDestroy(&obj->q2);free(obj);obj=NULL;
}/*** Your MyStack struct will be instantiated and called as such:* MyStack* obj = myStackCreate();* myStackPush(obj, x);* int param_2 = myStackPop(obj);* int param_3 = myStackTop(obj);* bool param_4 = myStackEmpty(obj);* myStackFree(obj);
*/
3.2、用栈实现队列
https://leetcode.cn/problems/implement-queue-using-stacks/description/
思路:入数据就在一个栈里入,数据导入另一个栈,出数据就在另一个栈里出

//定义栈的结构
typedef int STDataType;
typedef struct Stack {STDataType* arr;//数组int top;//指向栈顶的位置 -- 刚好就是栈中有效数据个数int capacity;//栈的空间大小
}ST;//栈的初始化
void STInit(ST* ps)
{ps->arr = NULL;ps->top = ps->capacity = 0;
}//栈的销毁
void STDesTroy(ST* ps)
{if (ps->arr)free(ps->arr);ps->arr = NULL;ps->top = ps->capacity = 0;
}//入栈 -- 栈顶
void STPush(ST* ps, STDataType x)
{assert(ps);//判断空间是否足够if (ps->top == ps->capacity){//增容int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;STDataType* tmp = (STDataType*)realloc(ps->arr, newCapacity * sizeof(STDataType));if (tmp == NULL){perror("realloc fail!");exit(1);}ps->arr = tmp;ps->capacity = newCapacity;}//空间足够ps->arr[ps->top] = x;ps->top++;
}//判断栈是否为空
bool STEmpty(ST* ps)
{assert(ps);return ps->top == 0;
}//出栈 -- 栈顶
void STPop(ST* ps)
{assert(!STEmpty(ps));//栈不为空ps->top--;
}//取栈顶元素
STDataType STTop(ST* ps)
{//栈不能为空,否则取不了assert(!STEmpty(ps));return ps->arr[ps->top - 1];
}//获取栈中有效元素个数
int STSize(ST* ps)
{assert(ps);return ps->top;
}
//-------以上是栈结构的定义和常见的方法-------typedef struct {ST pushST;ST popST;
} MyQueue;MyQueue* myQueueCreate() {MyQueue* pq = (MyQueue*)malloc(sizeof(MyQueue));STInit(&pq->pushST);STInit(&pq->popST);return pq;
}//入队列
void myQueuePush(MyQueue* obj, int x) {//往pushST中插入数据STPush(&obj->pushST,x);
}//出队列
int myQueuePop(MyQueue* obj) {//popST为空 -- 将pushST(不为空)导入到popSTif(STEmpty(&obj->popST)){while(!STEmpty(&obj->pushST)){//去栈顶,入popST,出栈STPush(&obj->popST,STTop(&obj->pushST));STPop(&obj->pushST);}}//popST不为空直接出int top = STTop(&obj->popST);STPop(&obj->popST);return top;
}int myQueuePeek(MyQueue* obj) {//popST为空 -- 将pushST(不为空)导入到popSTif(STEmpty(&obj->popST)){while(!STEmpty(&obj->pushST)){//去栈顶,入popST,出栈STPush(&obj->popST,STTop(&obj->pushST));STPop(&obj->pushST);}}//popST不为空直接出int top = STTop(&obj->popST);return top;
}bool myQueueEmpty(MyQueue* obj) {return STEmpty(&obj->popST) && STEmpty(&obj->pushST);
}void myQueueFree(MyQueue* obj) {STDesTroy(&obj->pushST);STDesTroy(&obj->popST);free(obj);obj = NULL;
}/*** Your MyQueue struct will be instantiated and called as such:* MyQueue* obj = myQueueCreate();* myQueuePush(obj, x);* int param_2 = myQueuePop(obj);* int param_3 = myQueuePeek(obj);* bool param_4 = myQueueEmpty(obj);* myQueueFree(obj);
*/