NzN的数据结构--队列的实现

         上一节中已经向大家介绍了栈的两种实现方法,今天我们来学习另一种线性存储结构--队列。先赞后看是好习惯!!!

目录

一、队列的概念及结构

二、用链表实现队列

1. 队列的初始化和销毁

2. 入队列

3. 出队列

4. 取队头

5. 取队尾

6. 判断队列是否为空

7. 计算队列大小

三、用数组实现队列

四、队列的应用


一、队列的概念及结构

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

        入队列:进行插入操作的一端称为队尾

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

二、用链表实现队列

        为了实现队列,我们需要一种数据结构,可以在一端添加元素,并在另一端删除元素,链表和数组都符合要求。那我们先来试试用链表实现队列。

        首先在头文件中声明队列的结果以及相关功能接口。

//Queue.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
typedef int QDataType;
typedef struct QueueNode
{
	int val;
	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);
//出队列
void QueuePop(Queue* pq);
//取队头
QDataType QueueFront(Queue* pq);
//取队尾
QDataType QueueBack(Queue* pq);
//判断队列是否为空
bool QueueEmpty(Queue* pq);
//计算队列大小
int QueueSize(Queue* pq);

1. 队列的初始化和销毁

void QueueInit(Queue* pq)
{
	assert(pq);
	pq->phead = NULL;
	pq->ptail = NULL;
	pq->size = 0;
}
void QueueDestroy(Queue* pq)
{
	assert(pq);
	QNode* cur = pq->phead;
	while (cur)
	{
		QNode* next = cur->next;
		//队列中元素依次释放
		free(cur);
		cur = next;
	}
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}

2. 入队列

void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc");
		exit(1);
	}
	newnode->val = x;
	newnode->next = NULL;
	if (pq->ptail)
	{
		//队列里已经有元素了
		pq->ptail->next = newnode;
		pq->ptail = newnode;
	}
	else
	{
		//插入的是队列的第一个元素
		pq->phead = pq->ptail = newnode;
	}
	pq->size++;
}

3. 出队列

void QueuePop(Queue* pq)
{
	assert(pq);
	//温柔检查
	/*if (pq->phead == NULL)
		return;*/
	//暴力检查(推荐)
	assert(pq->phead != NULL);
	//一个节点
	if (pq->phead->next == NULL)
	{
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
	}
	//多个节点
	else
	{
		QNode* next = pq->phead->next;
		free(pq->phead);
		pq->phead = next;
	}
	pq->size--;
}

4. 取队头

QDataType QueueFront(Queue* pq)
{
	assert(pq);
	// 暴力检查 
	assert(pq->phead != NULL);
	return pq->phead->val;
}

5. 取队尾

QDataType QueueBack(Queue* pq)
{
	assert(pq);
	// 暴力检查 
	assert(pq->ptail != NULL);
	return pq->ptail->val;
}

6. 判断队列是否为空

bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->size == 0;
}

7. 计算队列大小

int QueueSize(Queue* pq)
{
	assert(pq);
	return pq->size;
}

三、用数组实现队列

        我们可以使用一个变量front指向队首元素的索引,并维护一个变量size用于记录队列长度。定义rear=front+size ,这个公式计算出的rear指向队尾元素之后的下一个位置。

        基于此设计,数组中包含元素的有效区间为[front,rear-1]

  • 入队操作:将输入元素赋值给rear索引处,并将size+1 。
  • 出队操作:只需将front+1 ,并将size-1 。

       在不断进行入队和出队的过程中,front和rear都在向右移动,当它们到达数组尾部时就无法继续移动了。为了解决此问题,我们可以将数组视为首尾相接的“环形数组”。

        对于环形数组,我们需要让front或rear在越过数组尾部时,直接回到数组头部继续遍历。这种周期性规律可以通过“取余操作”来实现,代码如下所示:

typedef struct {
    int *nums;//用于存储队列元素的数组
    int front;//队首指针,指向队首元素
    int queSize;//尾指针,指向队尾 + 1
    int queCapacity;//队列容量
} ArrayQueue;

//队列初始化
ArrayQueue *QueueInit(int capacity) {
    ArrayQueue *queue = (ArrayQueue *)malloc(sizeof(ArrayQueue));
    // 化数组
    queue->queCapacity = capacity;
    queue->nums = (int *)malloc(sizeof(int) * queue->queCapacity);
    queue->front = queue->queSize = 0;
    return queue;
}

//销毁队列
void QueueDestroy(ArrayQueue *queue) {
    free(queue->nums);
    free(queue);
}

//判断队列是否为空
bool QueueEmpty(ArrayQueue *queue) {
    return queue->queSize == 0;
}

//访问队首元素
int QueueTop(ArrayQueue *queue) {
    assert(size(queue) != 0);
    return queue->nums[queue->front];
}

//入队
void QueuePush(ArrayQueue *queue, int num) {
    if (size(queue) == capacity(queue)) {
        printf("队列已满\r\n");
        return;
    }
    //计算队尾指针,指向队尾索引+1
    //通过取余操作实现rear越过数组尾部后回到头部
    int rear = (queue->front + queue->queSize) % queue->queCapacity;
    //将num添加至队尾
    queue->nums[rear] = num;
    queue->queSize++;
}

//出队
int QueuePop(ArrayQueue *queue) {
    int num = peek(queue);
    //队首指针向后移动一位,若越过尾部,则返回到数组头部
    queue->front = (queue->front + 1) % queue->queCapacity;
    queue->queSize--;
    return num;
}

        以上实现的队列仍然具有局限性:其长度不可变。然而,这个问题不难解决,我们可以将数组替换为动态数组,从而引入扩容机制。

【总结】

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

四、队列的应用

        队列最实际的应用就是保持公平性的排队。第一个排队的就是1号,下一个人的号就是队尾的号+1。如果两个人同时来到窗口取号,就会产生一个竞争,这里涉及到操作系统中的原子操作和加锁概念等,也可以叫做生产者消费者模型。

        除此之外,队列还可以用于广度优先遍历bfs,而深度优先遍历dfs常通过递归/栈来实现。

  • 淘宝订单。购物者下单后,订单将加入队列中,系统随后会根据顺序处理队列中的订单。在双十一期间,短时间内会产生海量订单,高并发成为工程师们需要重点攻克的问题。
  • 各类待办事项。任何需要实现“先来后到”功能的场景,例如打印机的任务队列、餐厅的出餐队列等,队列在这些场景中可以有效地维护处理顺序。
  • 40
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值