C语言数据结构——队列

目录

0.前言

1.队列的基本概念

2.队列的实现

2.1实现方式

2.2具体实现

3.队列的应用场景

4.一道队列的算法题(LeetCode225. 用队列实现栈)

5.结语


(图像由AI生成) 

0.前言

在计算机科学领域,数据结构是组织和存储数据的一种方式,它允许我们以有效的方式对数据进行访问和修改。今天,我们将探讨一种基础但极其重要的数据结构——队列。通过学习,我们不仅会了解队列的理论基础,还会深入其实现方式,探讨其应用场景,并通过解决一个实际问题来巩固我们的理解。

1.队列的基本概念

队列是一种基础但非常重要的线性数据结构,它在计算机科学和编程中有着广泛的应用。队列遵循先进先出(FIFO, First-In-First-Out)的原则,这意味着最先被加入队列的元素将是最先被移除的。这种特性使得队列非常适合于那些需要按照顺序处理元素的场景。

基本操作

队列的操作通常包括:

  • 入队(Push):在队列的末尾添加一个新的元素。
  • 出队(Pop):移除队列前端的元素。
  • 查看队首(Peek/Front):获取队列前端的元素,但不移除它。
  • 检查队列是否为空(IsEmpty):判断队列中是否有元素。
  • 获取队列大小(Size):获取队列中元素的数量。

特性

  • 有序性:队列保持元素的添加顺序,确保第一个加入的元素将是第一个被移除。
  • 动态性:队列可以动态地增长和缩减,随着元素的入队和出队操作。
  • 操作限制:在队列中,只能在一端(队尾)添加元素,在另一端(队首)移除元素。

2.队列的实现

2.1实现方式

队列可以通过不同的方式实现,其中最常见的两种是:

  1. 数组实现:使用数组存储队列中的元素。这种实现方式简单直观,但可能需要处理数组的动态扩容问题或者实现循环队列来优化空间利用。
  2. 链表实现:使用链表存储队列中的元素。链表实现的队列可以动态地增长,不需要担心空间限制,但每次操作可能涉及更多的内存分配和释放。

综合以上因素以及队列只需要“尾插”和“头删”的特性,我们最终决定用带头单向不循环链表来实现队列。

2.2具体实现

在这一部分,我们将详细介绍如何使用不带头单向不循环链表来实现队列。这种实现方式利用了链表的动态分配特性,允许队列在运行时根据需要增长或缩减。下面是具体的实现方法:

队列的结构定义

首先,我们定义了两个结构体:queueNodequeuequeueNode 结构体表示队列中的节点,包含数据域 _data 和指向下一个节点的指针 _nextqueue 结构体则表示队列本身,包含指向队列头部和尾部的指针 _head_tail

typedef struct queueNode
{QDataType _data;           // 节点存储的数据struct queueNode* _next;   // 指向下一个节点的指针
} queueNode;typedef struct queue
{queueNode* _head;          // 指向队列头部的指针queueNode* _tail;          // 指向队列尾部的指针
} queue;

初始化队列

queueInit 函数用于初始化队列,设置 _head_tail 指针都为 NULL,表示一个空队列。

void queueInit(queue* pq)
{assert(pq);pq->_head = pq->_tail = NULL;
}

销毁队列

queueDestroy 函数用于销毁队列,释放所有节点占用的内存,并将 _head_tail 指针重置为 NULL

void queueDestroy(queue* pq)
{queueNode* cur = pq->_head;while (cur){queueNode* next = cur->_next;free(cur);cur = next;}pq->_head = pq->_tail = NULL;
}

入队操作

queuePush 函数用于在队列尾部添加新节点。如果队列为空,新节点即成为队列的头部和尾部;否则,将新节点链接到原尾节点的 _next 指针,并更新 _tail 指针。

void queuePush(queue* pq, QDataType x)
{assert(pq);queueNode* newNode = (queueNode*)malloc(sizeof(queueNode));if (newNode == NULL){printf("malloc fail\n");exit(-1);}newNode->_data = x;newNode->_next = NULL;if (pq->_head == NULL){pq->_head = pq->_tail = newNode;}else{pq->_tail->_next = newNode;pq->_tail = newNode;}
}

出队操作

queuePop 函数用于移除队列头部的节点。如果队列在移除节点后变为空,需要同时将 _tail 指针置为 NULL

void queuePop(queue* pq)
{assert(pq && pq->_head);queueNode* next = pq->_head->_next;free(pq->_head);pq->_head = next;if (pq->_head == NULL){pq->_tail = NULL;}
}

另外我们提供了一些基础的队列操作函数,如 queueFrontqueueBack 分别返回队列的头部和尾部元素,queueEmpty 检查队列是否为空,queueSize 返回队列中元素的数量。完整代码如下:

//queue.h
#pragma once
#ifndef QUEUE_H
#define QUEUE_H
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int QDataType;
typedef struct queueNode
{QDataType _data;struct queueNode* _next;
}queueNode;typedef struct queue
{queueNode* _head;queueNode* _tail;
}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);//返回队尾元素
int queueEmpty(queue* pq);//判断队列是否为空,为空返回1,否则返回0
int queueSize(queue* pq);//返回队列中元素的个数#endif // !QUEUE_H
//queue.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"queue.h"
void queueInit(queue* pq)
{assert(pq);pq->_head = pq->_tail = NULL;
}void queueDestroy(queue* pq)
{queueNode* cur = pq->_head;while (cur){queueNode* next = cur->_next;free(cur);cur = next;}pq->_head = pq->_tail = NULL;
}void queuePush(queue* pq, QDataType x)
{assert(pq);queueNode* newNode = (queueNode*)malloc(sizeof(queueNode));if (newNode == NULL){printf("malloc fail\n");exit(-1);}newNode->_data = x;newNode->_next = NULL;if (pq->_head == NULL){pq->_head = pq->_tail = newNode;}else{pq->_tail->_next = newNode;pq->_tail = newNode;}
}void queuePop(queue* pq)
{assert(pq && pq->_head);queueNode*next = pq->_head->_next;free(pq->_head);pq->_head = next;if(pq->_head==NULL){pq->_tail = NULL;}
}QDataType queueFront(queue* pq)
{assert(pq && pq->_head);return pq->_head->_data;
}QDataType queueBack(queue* pq)
{assert(pq && pq->_tail);return pq->_tail->_data;
}int queueEmpty(queue* pq)
{assert(pq);return pq->_head == NULL ? 1 : 0;
}int queueSize(queue* pq)
{assert(pq);queueNode* cur = pq->_head;int count = 0;while (cur){count++;cur = cur->_next;}return count;
}

3.队列的应用场景

队列作为一种基本的数据结构,在计算机科学及其相关领域中有着广泛的应用。其先进先出(FIFO)的特性使得队列非常适合于处理需要按顺序执行的任务。以下是队列在不同领域中的一些典型应用场景:(以下查询自网络)

  • 操作系统
  • 在操作系统中,队列被用于多种任务调度和管理过程。例如,CPU调度算法如先来先服务(FCFS)直接使用队列的FIFO特性来管理进程。进程控制块(PCB)根据进程到达的顺序排队等待CPU时间。此外,I/O请求管理也常使用队列,设备请求按照到达顺序排队等待处理。
  • 网络通信
  • 在网络通信中,队列用于管理数据包的传输。数据包在进入网络设备如路由器或交换机时排队等待处理。这种机制帮助控制网络拥塞,确保数据包以合理的顺序被转发。
  • 异步编程
  • 在异步编程模型中,队列用于管理事件或消息。应用程序将事件放入队列中,事件循环依次处理这些事件。这种模型在JavaScript等语言的事件驱动编程中尤为常见。
  • 打印任务管理
  • 在打印任务管理中,打印任务按照提交的顺序排队等待打印。这确保了文档将按照用户提交的顺序被打印,避免了因资源竞争造成的混乱。

4.一道队列的算法题(LeetCode225. 用队列实现栈)

学习了栈和队列的知识之后,我们不妨来看一道简单的算法题——用队列实现栈。(链接在上面)

原题大致如下:

请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(pushtoppop 和 empty)。

实现 MyStack 类:

  • void push(int x) 将元素 x 压入栈顶。
  • int pop() 移除并返回栈顶元素。
  • int top() 返回栈顶元素。
  • boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。

注意:

  • 你只能使用队列的基本操作 —— 也就是 push to backpeek/pop from frontsize 和 is empty 这些操作。
  • 你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。

以下是用C语言的实现代码。由于C语言库函数没有“队列”,我们只能先“造”出一个队列。

typedef int QueueDataType;// 定义队列节点结构体
typedef struct QueueNode
{QueueDataType data;         // 节点存储的数据struct QueueNode* next;     // 指向下一个节点的指针
} Node;// 定义队列结构体
typedef struct Queue
{Node* head;                 // 指向队列头节点的指针Node* tail;                 // 指向队列尾节点的指针size_t size;                // 队列中元素的数量
} Queue;// 初始化队列
void QueueInit(Queue* q)
{// 创建哨兵节点q->head = q->tail = (Node*)malloc(sizeof(Node));q->head->next = NULL;q->size = 0;
}// 向队列中添加元素
void QueuePush(Queue* q, QueueDataType x)
{Node* newNode = (Node*)malloc(sizeof(Node));newNode->data = x;newNode->next = NULL;q->tail->next = newNode;q->tail = newNode;q->size++;
}// 从队列中移除元素
void QueuePop(Queue* q)
{assert(q->head->next != NULL); // 确保队列非空Node* toDelete = q->head->next;q->head->next = toDelete->next;if (q->tail == toDelete) // 如果移除的是尾节点,更新tail指向哨兵节点{q->tail = q->head;}free(toDelete);q->size--;
}// 获取队列头部元素
QueueDataType QueueFront(Queue* q)
{assert(q->head->next != NULL);return q->head->next->data;
}// 获取队列尾部元素
QueueDataType QueueBack(Queue* q)
{assert(q->head->next != NULL);return q->tail->data;
}// 检查队列是否为空
bool QueueEmpty(Queue* q)
{return q->head->next == NULL;
}// 获取队列中元素的数量
size_t QueueSize(Queue* q)
{return q->size;
}// 销毁队列,释放内存
void QueueDestroy(Queue* q)
{while (q->head->next != NULL){QueuePop(q);}free(q->head);q->head = q->tail = NULL;
}// 定义栈结构体,内部使用两个队列实现
typedef struct {Queue q1;   // 主队列Queue q2;   // 辅助队列,用于实现栈的pop和top操作
} MyStack;// 创建并初始化栈
MyStack* myStackCreate() {MyStack* stack = (MyStack*)malloc(sizeof(MyStack));QueueInit(&(stack->q1));QueueInit(&(stack->q2));return stack;
}// 向栈中添加元素
void myStackPush(MyStack* obj, int x) {if(QueueEmpty(&(obj->q2))) // 如果q2为空,向q1中添加元素{QueuePush(&(obj->q1), x);}else // 如果q1为空,向q2中添加元素{QueuePush(&(obj->q2), x);}
}// 从栈中移除元素
int myStackPop(MyStack* obj) {if(QueueEmpty(&(obj->q2))) // 如果q2为空,从q1中移动元素到q2,直到最后一个元素{while(obj->q1.size > 1){int num = QueueFront(&(obj->q1));QueuePop(&(obj->q1));QueuePush(&(obj->q2), num);}int ret = QueueFront(&(obj->q1));QueuePop(&(obj->q1));return ret;}else // 如果q1为空,从q2中移动元素到q1,直到最后一个元素{while(obj->q2.size > 1){int num = QueueFront(&(obj->q2));QueuePop(&(obj->q2));QueuePush(&(obj->q1), num);}int ret = QueueFront(&(obj->q2));QueuePop(&(obj->q2));return ret;}
}// 获取栈顶元素
int myStackTop(MyStack* obj) {if(QueueEmpty(&(obj->q2))) // 如果q2为空,栈顶元素在q1的尾部{return QueueBack(&(obj->q1));}else // 如果q1为空,栈顶元素在q2的尾部{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);
}

实现原理

该实现策略涉及两个队列,q1q2,它们交替作为主队列和辅助队列。这种方法的核心在于利用队列的先进先出(FIFO)特性来模拟栈的后进先出(LIFO)特性。

  • Push 操作:总是向当前非空的队列中添加新元素。如果两个队列都为空,则可以选择任意一个队列进行添加。这保证了所有元素都存储在同一个队列中,而另一个队列处于空闲状态,待下一次poptop操作时使用。

  • Pop 操作:将主队列中的所有元素(除最后一个元素)依次出队并入队到辅助队列中,然后移除并返回主队列中的最后一个元素。通过这种方式,可以实现栈的LIFO特性。操作完成后,主队列和辅助队列的角色会互换。

  • Top 操作:与pop操作类似,但是不移除最后一个元素,只返回其值。

  • Empty 操作:当两个队列都为空时,栈也为空。

代码解析

在以上代码中,Queue结构体用于实现队列的基本功能,而MyStack结构体则使用两个Queue实例来模拟栈的行为。

  • myStackPush函数向当前活跃的队列中添加元素。
  • myStackPop函数通过将主队列中的元素(除了最后一个)转移到辅助队列中,然后移除并返回最后一个元素,实现了栈的pop操作。此后,队列的角色将互换。
  • myStackTop函数与myStackPop类似,但是在返回最后一个元素的值后,不会将其移除。
  • myStackEmpty函数检查两个队列是否都为空,以确定栈是否为空。
  • myStackFree函数负责释放两个队列和栈实例所占用的内存资源。

5.结语

队列是数据结构中的基石之一,了解其原理和实现方式对于学习更复杂的数据结构和算法至关重要。通过实际编码和解决问题,我们可以加深对队列结构和其应用的理解。希望这篇博客能帮助你在数据结构的学习旅程中迈出坚实的一步。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/714632.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux篇: 进程控制

一、进程创建 1.1 fork函数初识 在Linux中&#xff0c;fork函数是非常重要的函数&#xff0c;它从已存在进程中创建一个新进程。新进程为子进程&#xff0c;而原进程为父进程。 返回值&#xff1a; 在子进程中返回0&#xff0c;父进程中返回子进程的PID&#xff0c;子进程创…

OSI七层模型/TCP四层模型

协议&#xff1a; 协议是双方共同指定的一组规则&#xff0c;在网络通信中表示通信双方传递数据和解释数据的一组规则。 从A上传文件到服务器B,需要在A和B之间制定一个双方都认可的规则&#xff0c;这个规则就叫文件传输协议&#xff0c;该协议是ftp协议的一个初级版本&#…

LeetCode 刷题 [C++] 第226题.翻转二叉树

题目描述 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 题目分析 深度优先搜索&#xff08;DFS&#xff09;- 递归方式 对于二叉树的镜像问题&#xff0c;很容易想到的就是使用递归来解决&#xff0c;自底向上依次翻转每一个节点…

2024年腾讯云优惠券领取页面_代金券使用方法_新老用户均可

腾讯云代金券领取渠道有哪些&#xff1f;腾讯云官网可以领取、官方媒体账号可以领取代金券、完成任务可以领取代金券&#xff0c;大家也可以在腾讯云百科蹲守代金券&#xff0c;因为腾讯云代金券领取渠道比较分散&#xff0c;腾讯云百科txybk.com专注汇总优惠代金券领取页面&am…

百度SEO快排原理是什么?如何快速排名方法?

前言&#xff1a;我之前说过我不打算写这个快速排序。 首先&#xff0c;我从来没有在自己的网站上操作过所谓的快速排序。 其次&#xff0c;我不能像网上很多人写的那样透露百度快速排序的秘密&#xff08;说实话&#xff0c;你可以透露秘密&#xff09;。 方法是有了&#xff…

Postman: 前端必备工具还是后端独享利器

Postman 的使用场景&#xff1a;适用于前端和后端 Postman 是一个流行的 API 测试与开发工具。它被广泛地应用在前后端开发的过程中&#xff0c;但是很多人对于它的使用场景存在疑惑。那么&#xff0c;到底是前端用还是后端用呢&#xff1f;本文将从多个角度详细解答这个问题。…

【嵌入式——QT】QListWidget

QListWidget类提供了一个基于项的列表小部件&#xff0c;QListWidgetItem是列表中的项&#xff0c;该篇文章中涉及到的功能有添加列表项&#xff0c;插入列表项&#xff0c;删除列表项&#xff0c;清空列表&#xff0c;向上移动列表项&#xff0c;向下移动列表项。 常用API a…

C语言数据结构基础——双链表专题

前言 书接上回&#xff0c;双链表便是集齐带头、双向、循环等几乎所有元素的单链表PLUS. 1.初始化、创建双链表 typedef int LTDataType; typedef struct LTNode {LTDataType data;struct LTNode* next;struct LTNode* prev; }LTNode; 不同于单链表&#xff0c;此时每个节点应…

PySide6+VSCode Python可视化环境搭建

#记住在cmd中运行&#xff0c;不要在vscode里运行&#xff0c;否则env会装到工程目录下 python -m venv env #env\Scripts\activate.bat pip install pyside6 下载本期源码 vscode装一个PYQT Integration插件&#xff0c;设置好两个路径&#xff08;下面有个脚本用于获取路径&…

MySQL 数据库表设计和优化

一、数据结构设计 正确的数据结构设计对数据库的性能是非常重要的。 在设计数据表时&#xff0c;尽量遵循一下几点&#xff1a; 将数据分解为合适的表&#xff0c;每个表都应该有清晰定义的目的&#xff0c;避免将过多的数据存储在单个表中。使用适当的数据类型来存储数据&…

挑战杯 基于机器视觉的车道线检测

文章目录 1 前言2 先上成果3 车道线4 问题抽象(建立模型)5 帧掩码(Frame Mask)6 车道检测的图像预处理7 图像阈值化8 霍夫线变换9 实现车道检测9.1 帧掩码创建9.2 图像预处理9.2.1 图像阈值化9.2.2 霍夫线变换 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分…

范伟:你们怎么老提1,200呢,有什么典故啊?赵本山:没有啊!

范伟&#xff1a;你们怎么老提1,200呢,有什么典故啊?赵本山&#xff1a;没有啊&#xff01; --小品《面子》&#xff08;中3&#xff09;的台词 表演者&#xff1a;赵本山 高秀敏 范伟 &#xff08;接上&#xff09; 范伟&#xff1a;哎吃啊 赵&#xff1a;哎呀这电视看的挺…

cAdvisor+Prometheus+Grafana 搞定Docker容器监控平台

cAdvisorPrometheusGrafana cAdvisorPrometheusGrafana 搞定Docker容器监控平台1、先给虚拟机上传cadvisor2、What is Prometheus?2.1、架构图 3、利用docker安装普罗米修斯4、安装grafana cAdvisorPrometheusGrafana 搞定Docker容器监控平台 1、先给虚拟机上传cadvisor cAd…

MySQL事务和锁机制

MySQL技术——事务和锁机制 一、事务&#xff08;1&#xff09;概述&#xff08;2&#xff09;ACID特性&#xff08;3&#xff09;事务并发存在的问题&#xff08;4&#xff09;事务的隔离级别 二、锁机制&#xff08;1&#xff09;锁的力度&#xff08;2&#xff09;表的分类&…

网络编程-编码与解码(Protobuf)

编码与解码 下面的文字都来自于极客时间 为什么要编解码呢&#xff1f;因为计算机数据传输的是二进制的字节数据 解码&#xff1a;字节数据 --> 字符串&#xff08;字符数据&#xff09; 编码&#xff1a;字符串&#xff08;字符数据&#xff09;–> 字节数据 我们在编…

公共字段自动填充

在开发中经常面临对于一些公共字段的赋值。 如在下表中&#xff1a; 如何让程序自动为我们需要赋值的公共字段进行赋值&#xff0c;避免在业务代码中重复写这些公共字段的赋值代码 如下图所示&#xff1a; 实现思路&#xff1a; 1.自定义注解AutoFill&#xff0c;用于标识需…

linux环境安装cuda toolkit

1 全新安装 如果环境中没安装过cuda版本&#xff0c; 这种情况下比较简单。 直接在https://developer.nvidia.com/cuda-toolkit-archive选择对应版本下载安装即可。 如下为安装cuda toolkit 11.8. 2 环境中已经存在其他版本 这种情况下比较复杂一些。 首先要确认最高支持的…

李沐动手学习深度学习——4.2练习

1. 在所有其他参数保持不变的情况下&#xff0c;更改超参数num_hiddens的值&#xff0c;并查看此超参数的变化对结果有何影响。确定此超参数的最佳值。 通过改变隐藏层的数量&#xff0c;导致就是函数拟合复杂度下降&#xff0c;隐藏层过多可能导致过拟合&#xff0c;而过少导…

【MySQL】表的内连和外连(重点)

表的连接分为内连和外连。 一、内连接 内连接实际上就是利用 where 子句对两种表形成的笛卡儿积进行筛选&#xff0c;前面学习的查询都是内连接&#xff0c;也是在开发过程中使用的最多的连接查询。 select 字段 from 表1 inner join 表2 on 连接条件 and 其他条件; 注意&…

Linux使用基础命令

1.常用系统工作命令 (1).用echo命令查看SHELL变量的值 qiangziqiangzi-virtual-machine:~$ echo $SHELL /bin/bash(2).查看本机主机名 qiangziqiangzi-virtual-machine:~$ echo $HOSTNAME qiangzi-virtual-machine (3).date命令用于显示/设置系统的时间或日期 qiangziqian…