【数据结构】队列“0”基础知识讲解 + 实战演练

一、概念与结构

概念:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)

入队列:进行插入操作的一端称为队尾
出队列:进行删除操作的一端称为队头

队列底层结构选型

队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。

我们选择链表而不选择数组有如下几点优势:

1. 内存动态分配与灵活扩展性

  • 链表的动态特性 :链表在内存中是通过节点的指针连接起来的,每个节点都是在需要时通过动态内存分配(如 C 语言中的malloc)获得。这意味着链表的长度可以根据实际需求随时增加或减少,无需预先指定固定的大小。例如,在处理一个元素数量不确定的任务队列时,随着任务的不断加入和完成,链表能够轻松适应元素数量的变化,不会出现像数组那样因空间不足而需要重新分配内存并复制数据的情况。
  • 数组的固定空间限制:数组在定义时需要指定大小,一旦确定,其占用的内存空间就固定下来。如果事先分配的空间过大,会造成内存浪费;若分配空间过小,当队列元素数量超过数组容量时,就需要重新分配更大的数组,并将原数组中的元素复制到新数组中,这会增加时间和空间开销。

2. 插入和删除操作效率

  • 链表的高效插入删除:在链表中进行插入和删除操作时,只需修改相关节点的指针指向即可。对于队列的入队操作(在队尾插入元素)和出队操作(在队头删除元素),时间复杂度均为 O(1) 。以单链表实现队列为例,入队时创建新节点并将其链接到队尾,出队时将头节点指向下一个节点并释放原头节点。
  • 数组的插入删除开销:在数组中进行插入和删除操作,尤其是在数组头部或中间位置时,需要移动大量元素来保持数组的连续性。对于队列的出队操作(删除队头元素),后续元素都需要向前移动,时间复杂度为 O(n),其中 n 是队列中元素的个数。这在元素数量较多时,会显著影响操作效率。

3. 内存碎片化影响

  • 链表受碎片化影响小:链表的节点在内存中是分散存储的,即使内存中存在一些碎片化的空闲空间,也能利用这些空间来分配新的节点。只要系统还有可用内存,就能继续创建节点,不会因为内存碎片化导致无法为队列分配空间。
  • 数组受碎片化影响大:数组要求连续的内存空间,当内存出现碎片化时,即使总的空闲内存足够,但如果没有一块连续的足够大的空间,就无法为数组扩容,从而可能导致队列无法正常工作。

4. 内存使用的针对性

  • 链表按需分配:链表可以根据实际存储的元素数量来分配内存,每个节点只存储数据和指针,对于元素数量较少的队列,不会占用过多的额外空间。
  • 数组的预分配浪费:数组不管实际存储多少元素,都需要预先分配固定大小的空间,当队列中元素数量远小于数组大小时,会造成内存的浪费。

不过,链表也并非完全没有缺点,比如它相较于数组,访问元素时只能顺序遍历,无法像数组那样通过下标直接随机访问,而且每个节点需要额外存储指针,会占用一定的内存空间。

二、队列的实现(重点)

2.1、Queue.h
cpp 复制代码
#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
cpp 复制代码
#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
cpp 复制代码
#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/

思路:俩个队列配合使用
cpp 复制代码
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))
    {
        //q1
        QueuePush(&obj->q1,x);
    }
    else
    {
        //q2
        QueuePush(&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/

思路:入数据就在一个栈里入,数据导入另一个栈,出数据就在另一个栈里出
cpp 复制代码
//定义栈的结构
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(不为空)导入到popST
    if(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(不为空)导入到popST
    if(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);
*/
相关推荐
无限进步_4 小时前
【C语言】函数指针数组:从条件分支到转移表的优雅进化
c语言·开发语言·数据结构·后端·算法·visual studio
报错小能手4 小时前
项目——基于C/S架构的预约系统平台(2)
linux·c语言·笔记·学习·架构
小小测试开发4 小时前
Bokeh 库入门:用 Python 绘制交互式数据可视化图表
开发语言·python·信息可视化·bokeh
hoiii1874 小时前
C#实现摄像头视频录制与保存
开发语言·c#·音视频
数据科学作家4 小时前
如何入门python机器学习?金融从业人员如何快速学习Python、机器学习?机器学习、数据科学如何进阶成为大神?
大数据·开发语言·人工智能·python·机器学习·数据分析·统计分析
Q741_1474 小时前
C++ 分治 快速选择算法 堆排序 TopK问题 力扣 215. 数组中的第K个最大元素 题解 每日一题
c++·算法·leetcode·分治·1024程序员节·topk问题·快速选择算法
孤客网络科技工作室4 小时前
Python - 100天从新手到大师:第五十八天 Python中的并发编程(1-3)
开发语言·python
文火冰糖的硅基工坊4 小时前
[人工智能-大模型-57]:模型层技术 - 软件开发的不同层面(如底层系统、中间件、应用层等),算法的类型、设计目标和实现方式存在显著差异。
人工智能·算法·中间件
鱼儿也有烦恼4 小时前
快速学完 LeetCode top 1~50 [特殊字符]
java·算法·leetcode·1024程序员节