【排排站:探索数据结构中的队列奇象】

本章重点

  • 队列的概念及结构

  • 队列的实现方式

  • 链表方式实现栈接口

  • 队列面试题

一、队列的概念及结构

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

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

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

  • 队头:线性表允许删除的那一端。
  • 队尾:线性表允许插入的那一端。
  • 空队:不含任何元素的空表。

二、队列的实现方式

        如图3-5所示为依次向队列中插入元素 a0,a1,…,an-1后的示意图,其中,a0是当前队头元素,an-1 是当前队尾元素。

        就像在食堂买饭就餐一样,如果你在就餐人不多时去食堂就餐,你一到买饭窗口就能得到食堂服务人员的服务;但如果你在就餐人很多时去食堂就餐,你就需要在某个窗口排队等待,直到轮到你时才能得到食堂服务人员的服务。在软件设计中也经常会遇到需要排队等待服务的问题。队列可用于临时存储那些需要等待接受服务的信息序列。

        队列只允许在头部插入,尾部删除,因此队列的实现一般可以使用数组或者链表实现。

1.顺序队列

如图3-6所示为一个有6个内存单元的顺序队列的动态示意图,图中front为队头指针,rear为队尾指针。图3-6 (a)表示一个空队列;图3-6 (b)表示A、B、C入队列后的状态;图3-6 (c) 为A、B出队列后的状态;图3-6 (d) 为D,E入队列后的状态。

 2.链式队列

        我们已知,队列是操作受限制的线性表,队列有队头和队尾,插入元素的一端称为队尾,
删除元素的一端称为队头。
        链式队列的队头指针指向队列的当前头结点位置,队尾指针指向队列的当前队尾结点位置。对于不带头结点的链式队列,出队列时可直接删除队头指针所指的结点,因此链式队列没有头结点更方便。一个不带头结点、队列中有元素a0,a1,…,an-1的链式队列的结构如图3-9所示,其中,指针 front 指示的是链式队列的队头结点,指针 rear 指示的是链式队列的队尾结点。

三、链表方式实现栈接口

由于队列只允许在头部插入,尾部删除,因此我们会改变头结点,前面我们学过用二级指针和返回值两种方式来处理头结点改变,今天我们来学一种新方式:结构体修改,将队列的头结点和尾结点放入到一个结构体当中,通过结构体地址就可以修改结构体的内容,同时还加入了一个size,用来计算当前队列的长度。

typedef int QDataType;// 单链式结构:表示队列
typedef struct QListNode
{struct QListNode* Next;QDataType data;
}QNode;// 队列的结构
typedef struct Queue
{QNode* front;QNode* rear;int size;
}Queue;// 初始化队列
void QueueInit(Queue * q);
// 队尾入队列
void QueuePush(Queue* q, QDataType data);
// 队头出队列
void QueuePop(Queue* q);
// 获取队列头部元素
QDataType QueueFront(Queue* q);
// 获取队列队尾元素
QDataType QueueBack(Queue* q);
// 获取队列中有效元素个数
int QueueSize(Queue* q);
// 检测队列是否为空
bool QueueEmpty(Queue* q);
// 销毁队列
void QueueDestroy(Queue* q);

1.初始化队列:void QueueInit(Queue* q)

直接将front和rear域都设置为NULL,将队列长度设置为0,

// 初始化队列
void QueueInit(Queue* q)
{assert(q);q->front = q->rear = NULL;q->size = 0;
}

2.队尾入队列:void QueuePush(Queue* q, QDataType data)

构造一个节点newnode,data域存储数据,next域存储NULL,若原链队为空,则将链队结点的两个域都指向结点newnode,否则将结点newnode链接到单链表末尾,并让链队结点的rear域指向它,再让队列的长度+1

// 队尾入队列
void QueuePush(Queue* q, QDataType data)
{assert(q);QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL){perror("malloc fail");exit(-1);}newnode->data = data;newnode->Next = NULL;if (q->rear == NULL){q->front = q->rear = newnode;}else{q->rear->Next = newnode;q->rear = newnode;}q->size++;
}

3.队头出队列:void QueuePop(Queue* q)

若原链队为空,则下溢,assert断言提示错误,否则将队首结点的Next域赋值给next,并删除队首结点,若原链队只有一个结点,则需要将链队结点的两个域都设置为NULL,表示此时链队已空。然后队列长度-1.

// 队头出队列
void QueuePop(Queue* q)
{assert(q);//队列为空,断言assert(!QueueEmpty(q));//rear出现野指针的问题if (q->front->Next == NULL){free(q->front);q->front = q->rear = NULL;}else{QNode* next = q->front->Next;free(q->front);q->front = next;}q->size--;
}

4.获取队列头部元素:QDataType QueueFront(Queue* q)

// 获取队列头部元素
QDataType QueueFront(Queue* q)
{assert(q);//队列为空,断言assert(!QueueEmpty(q));return q->front->data;
}

5.获取队列队尾元素:QDataType QueueBack(Queue* q)

// 获取队列队尾元素
QDataType QueueBack(Queue* q)
{assert(q);//队列为空,断言assert(!QueueEmpty(q));return q->rear->data;
}

6.获取队列中有效元素个数:int QueueSize(Queue* q)

// 获取队列中有效元素个数
int QueueSize(Queue* q)
{assert(q);return q->size;
}

7.检测队列是否为空:bool QueueEmpty(Queue* q)

// 检测队列是否为空
bool QueueEmpty(Queue* q)
{assert(q);//头为空,该队列就为空//返回true - 队列就为空//返回false - 队列不为空return q->front == NULL;
}

8.销毁队列:void QueueDestroy(Queue* q)

销毁队列创建一个结点保存下个结点的值,释放当前结点,然后依次遍历队列,依次释放结点。释放后需要将队头和队尾都置空,队列长度设置为0,由于是通过结构体去修改头结点,此时队列已经为空指针,在调用函数后,不需要手动将头指针置空。

// 销毁队列
void QueueDestroy(Queue* q)
{assert(q);QNode* cur = q->front;while (cur){QNode* next = cur->Next;free(cur);cur = next;}q->front = q->rear = NULL;q->size = 0;
}

四、队列面试题

1. 用队列实现栈。OJ链接

  • 栈是一种后进先出的数据结构,元素从顶端入栈,然后从顶端出栈。
  • 队列是一种先进先出的数据结构,元素从后端入队,然后从前端出队。

思路:

        为了满足栈的特性,即最后入栈的元素最先出栈,在使用队列实现栈时,应满足队列前端的元素是最后入栈的元素。可以使用两个队列实现栈的操作,其中 queue1用于存储栈内的元素,queue2作为入栈操作的辅助队列。

        入栈操作时,首先将元素入队到 queue2 ,然后将 queue1的全部元素依次出队并入队到 queue2此时 queue2的前端的元素即为新入栈的元素,再将 queue1和 queue2 互换,则 queue1​ 的元素即为栈内的元素,queue1的前端和后端分别对应栈顶和栈底。

        由于每次入栈操作都确保 queue1的前端元素为栈顶元素,因此出栈操作和获得栈顶元素操作都可以简单实现。出栈操作只需要移除 queue1的前端元素并返回即可,获得栈顶元素操作只需要获得 queue1的前端元素并返回即可(不移除元素)。

        由于 queue1用于存储栈内的元素,判断栈是否为空时,只需要判断 queue1是否为空即可。

typedef struct {int* stk;int stkSize;int stkCapacity;
} Stack;Stack* stackCreate(int cpacity) {Stack* ret = malloc(sizeof(Stack));ret->stk = malloc(sizeof(int) * cpacity);ret->stkSize = 0;ret->stkCapacity = cpacity;return ret;
}void stackPush(Stack* obj, int x) {obj->stk[obj->stkSize++] = x;
}void stackPop(Stack* obj) {obj->stkSize--;
}int stackTop(Stack* obj) {return obj->stk[obj->stkSize - 1];
}bool stackEmpty(Stack* obj) {return obj->stkSize == 0;
}void stackFree(Stack* obj) {free(obj->stk);
}typedef struct {Stack* inStack;Stack* outStack;
} MyQueue;MyQueue* myQueueCreate() {MyQueue* ret = malloc(sizeof(MyQueue));ret->inStack = stackCreate(100);ret->outStack = stackCreate(100);return ret;
}void in2out(MyQueue* obj) {while (!stackEmpty(obj->inStack)) {stackPush(obj->outStack, stackTop(obj->inStack));stackPop(obj->inStack);}
}void myQueuePush(MyQueue* obj, int x) {stackPush(obj->inStack, x);
}int myQueuePop(MyQueue* obj) {if (stackEmpty(obj->outStack)) {in2out(obj);}int x = stackTop(obj->outStack);stackPop(obj->outStack);return x;
}int myQueuePeek(MyQueue* obj) {if (stackEmpty(obj->outStack)) {in2out(obj);}return stackTop(obj->outStack);
}bool myQueueEmpty(MyQueue* obj) {return stackEmpty(obj->inStack) && stackEmpty(obj->outStack);
}void myQueueFree(MyQueue* obj) {stackFree(obj->inStack);stackFree(obj->outStack);
}

2. 用栈实现队列。OJ链接

将一个栈当作输入栈,用于压入 push\ 传入的数据;另一个栈当作输出栈,用于 pop 和 peek 操作。每次 pop 或 peek 时,若输出栈为空则将输入栈的全部数据依次弹出并压入输出栈,这样输出栈从栈顶往栈底的顺序就是队列从队首往队尾的顺序。

typedef struct {int* stk;int stkSize;int stkCapacity;
} Stack;Stack* stackCreate(int cpacity) {Stack* ret = malloc(sizeof(Stack));ret->stk = malloc(sizeof(int) * cpacity);ret->stkSize = 0;ret->stkCapacity = cpacity;return ret;
}void stackPush(Stack* obj, int x) {obj->stk[obj->stkSize++] = x;
}void stackPop(Stack* obj) {obj->stkSize--;
}int stackTop(Stack* obj) {return obj->stk[obj->stkSize - 1];
}bool stackEmpty(Stack* obj) {return obj->stkSize == 0;
}void stackFree(Stack* obj) {free(obj->stk);
}typedef struct {Stack* inStack;Stack* outStack;
} MyQueue;MyQueue* myQueueCreate() {MyQueue* ret = malloc(sizeof(MyQueue));ret->inStack = stackCreate(100);ret->outStack = stackCreate(100);return ret;
}void in2out(MyQueue* obj) {while (!stackEmpty(obj->inStack)) {stackPush(obj->outStack, stackTop(obj->inStack));stackPop(obj->inStack);}
}void myQueuePush(MyQueue* obj, int x) {stackPush(obj->inStack, x);
}int myQueuePop(MyQueue* obj) {if (stackEmpty(obj->outStack)) {in2out(obj);}int x = stackTop(obj->outStack);stackPop(obj->outStack);return x;
}int myQueuePeek(MyQueue* obj) {if (stackEmpty(obj->outStack)) {in2out(obj);}return stackTop(obj->outStack);
}bool myQueueEmpty(MyQueue* obj) {return stackEmpty(obj->inStack) && stackEmpty(obj->outStack);
}void myQueueFree(MyQueue* obj) {stackFree(obj->inStack);stackFree(obj->outStack);
}

3. 设计循环队列。OJ链接

顺序队列的假溢出问题

        解决假溢出的方法就是后面满了,就再从头开始,也就是头尾相接的循环。我们把队列的这种头尾相接的顺序存储结构称为循环队列。当队首指针Q->front = MAXSIZE-1后,再前进一个位置就自动到0,这可以利用除法取余运算(%)来实现。

  • 初始时:Q->front = Q->rear=0。
  • 队首指针进1:Q->front = (Q->front + 1) % MAXSIZE。
  • 队尾指针进1:Q->rear = (Q->rear + 1) % MAXSIZE。
  • 队列长度:(Q->rear - Q->front + MAXSIZE) % MAXSIZE。

        出队入队时,指针都按照顺时针方向前进1,如下图所示:


        那么,循环队列队空和队满的判断条件是什么呢?
        显然,队空的条件是 Q->front == Q->rear 。若入队元素的速度快于出队元素的速度,则队尾指针很快就会赶上队首指针,如图( d1 )所示,此时可以看出队满时也有 Q ->front == Q -> rear 。为了区分队空还是队满的情况,有三种处理方式:
(1)牺牲一个单元来区分队空和队满,入队时少用一个队列单元,这是种较为普遍的做法,约定以“队头指针在队尾指针的下一位置作为队满的标志”,如图 ( d2 )所示。

  • 队满条件: (Q->rear + 1)%Maxsize == Q->front
  • 队空条件仍: Q->front == Q->rear
  • 队列中元素的个数: (Q->rear - Q ->front + Maxsize)% Maxsize

(2)类型中增设表示元素个数的数据成员。这样,队空的条件为 Q->size == O ;队满的条件为 Q->size == Maxsize 。这两种情况都有 Q->front == Q->rear
(3)类型中增设tag 数据成员,以区分是队满还是队空。tag 等于0时,若因删除导致 Q->front == Q->rear ,则为队空;tag 等于 1 时,若因插入导致 Q ->front == Q->rear ,则为队满。

typedef struct {int front;int rear;int capacity;int *elements;
} MyCircularQueue;MyCircularQueue* myCircularQueueCreate(int k) {MyCircularQueue *obj = (MyCircularQueue *)malloc(sizeof(MyCircularQueue));obj->capacity = k + 1;obj->rear = obj->front = 0;obj->elements = (int *)malloc(sizeof(int) * obj->capacity);return obj;
}bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {if ((obj->rear + 1) % obj->capacity == obj->front) {return false;}obj->elements[obj->rear] = value;obj->rear = (obj->rear + 1) % obj->capacity;return true;
}bool myCircularQueueDeQueue(MyCircularQueue* obj) {if (obj->rear == obj->front) {return false;}obj->front = (obj->front + 1) % obj->capacity;return true;
}int myCircularQueueFront(MyCircularQueue* obj) {if (obj->rear == obj->front) {return -1;}return obj->elements[obj->front];
}int myCircularQueueRear(MyCircularQueue* obj) {if (obj->rear == obj->front) {return -1;}return obj->elements[(obj->rear - 1 + obj->capacity) % obj->capacity];
}bool myCircularQueueIsEmpty(MyCircularQueue* obj) {return obj->rear == obj->front;
}bool myCircularQueueIsFull(MyCircularQueue* obj) {return (obj->rear + 1) % obj->capacity == obj->front;
}void myCircularQueueFree(MyCircularQueue* obj) {free(obj->elements);free(obj);
}

本章结束啦!!!

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

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

相关文章

“RFID与光伏板的完美融合:探索能源科技的新时代!“

随着科技的不断发展,人类创造出了许多令人惊叹的发明。其中,RFID(Radio Frequency Identification)技术的应用在各个领域日益广泛。最近的研究表明,将RFID技术应用于光伏板领域,不仅可以提高光伏板的效率&a…

JVM中分代回收机制

为什么要分为新生代和老年代? 分为新生代(Young Generation)和老年代(Old Generation)是为了更有效地管理和优化内存的使用。 新生代主要存放生命周期较短的对象,例如方法的局部变量、临时变量等。由于这…

【Golang系统开发】搜索引擎(2) 压缩词典

写在前面 这篇文章我们就给出一系列的数据结构,使得词典能达到越来越高的压缩比。当然,和倒排索引记录表的大小相比,词典只占据了非常小的空间。那么为什么要对词典进行压缩呢? 这是因为决定信息检索系统的查询响应时间的一个重…

李沐pytorch学习-卷积网络及其实现

一、卷积概述 1.1 基本定义 卷积计算过程如图1所示,即输入矩阵和核函数(filter)对应的位置相乘,然后相加得到输出对应位置的数。 图1. 卷积计算过程 该过程可以形象地从图2中展现。 图2. 二维卷积示意图 1.2 实现互相关运算的代…

Python tkinter Notebook标签添加关闭按钮元素,及左侧添加存储状态提示图标案例,类似Notepad++页面

效果图展示 粉色框是当前页面,橙色框是鼠标经过,红色框是按下按钮,灰色按钮是其他页面的效果; 存储标识可以用来识别页面是否存储:例如当前页面已经保存用蓝色,未保存用红色,其他页面已经保存用…

2023最新版本~KEIL5使用C++开发STM32

先看效果 开始教学 因为是第一次写这个配置教程 我会尽量详细些 打开一个Keil工程 移除本地core 添加在线core 第一次编译代码 不会有报错 修改main.c文件类型为C 点击魔术棒 把ARM编译器修改为V6 第二次编译会报错语法不兼容 我把汇编部分的这些代码做了…

基于IMX6ULLmini的linux裸机开发系列九:时钟控制模块

时钟控制模块 核心 4个层次配置芯片时钟 晶振时钟 PLL与PFD时钟 PLL选择时钟 根时钟/外设时钟 系统时钟来源 RTC时钟源:32.768KHz 系统时钟:24MHz,作为芯片的主晶振使用 PLL和PFD倍频时钟 7路锁相环电路(每个锁相环电路…

【IMX6ULL驱动开发学习】05.字符设备驱动开发模板(包括读写函数、poll机制、异步通知、定时器、中断、自动创建设备节点和环形缓冲区)

一、 字符设备驱动简介 字符设备是Linux驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节流进行读写操作的设备,读写数据是分先后顺序的。比如常见的点灯、按键、IIC、SPI、LCD 等等都是字符设备,这些设备的驱动就叫…

centos8 使用phpstudy安装tomcat部署web项目

系统配置 1、安装Tomcat 2、问题 正常安装完Tomcat应该有个配置选项,用来配置server.xml web.xml 还有映射webapps路径选项,但是我用的这个版本并没有。所以只能曲线救国。 3、解决 既然没有配置项,那就只能按最基本的方法配置&#xff0c…

关于Coursera网站视频无法观看

文章目录 前言找Ip 改hosts验证 前言 众所周知,coursera是很不错的学习网站,但由于国内访问限制,导致我的学习之路举步维艰 在科学上网彻底崩盘后,终于断了我的学习热情(真的很想骂人) 网站只能登入&#…

【无标题】WIN11下 ESP8266 _RTOS_SDK3.0以上开发环境搭建(记录及避坑必看)

前提参考文档 1、乐鑫官网: https://docs.espressif.com/projects/esp8266-rtos-sdk/en/latest/get-started/index.html 官网上有如何搭建windows linux macos 三种环境,以及如何配置Eclipse去编译和开发项目(如何安装Eclipse环境&#xff0…

微人事 登录问题完善

重启服务端的时候,发现前端页面会操作不了,这样后端session会失效,我们就需要让页面重新跳转到登录页 springsecurity配置类后端配置 前端拦截器进行拦截跳转

【STM32RT-Thread零基础入门】 5. 线程创建应用(线程创建、删除、初始化、脱离、启动、睡眠)

硬件:STM32F103ZET6、ST-LINK、usb转串口工具、4个LED灯、1个蜂鸣器、4个1k电阻、2个按键、面包板、杜邦线 文章目录 前言一、线程管理接口介绍二、任务:使用多线程的方式同时实现led闪烁和按键控制喇叭(扫描法)1. RT-Thread相关接…

使用mysql:5.6和owncloud镜像构建个人网盘

一、拉取镜像 使用docker拉取mysql:5.6和owncloud的镜像 [rootexam ~]# docker pull mysql:5.6 [rootexam ~]# docker pull owncloud 运行镜像生成容器实例 [rootexam ~]# docker run -d --name mydb1 --env MYSQL_ROOT_PASSWORD123456 mysql:5.6 a184c65b73ff993cc5cf86f…

700. 二叉搜索树中的搜索

给定二叉搜索树(BST)的根节点 root 和一个整数值 val。 你需要在 BST 中找到节点值等于 val 的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 null 。 示例 1: 输入:root [4,2,7,1,3], val 2 输出:[2,1,3]…

vue3生命周期

原理 vue3也提供了Composition API形式的生命周期钩子,与vue2.x中钩子对应关系如下: beforeCreate setup() created setup() beforeMountonBeforeMount mountedonMounted beforeUpdateonBeforeUpdate updat…

docker的安装与基础使用

一.docker简介 1)什么是docker Docker是一种用于构建、打包和运行应用程序的开源平台。它基于操作系统级虚拟化技术,可以将应用程序和其依赖的库、环境等资源打包到一个可移植的容器中,形成一个轻量级、独立的可执行单元。 开发者在本地编…

MySQL流程控制

流程控制 顺序结构: 程序从上往下依次执行分支结构: 程序按条件进行选择执行,从两条或多条路径中选择一条执行。循环结构: 程序满足一定条件下,重复执行一组语句 针对于MySQL的流程控制语句主要有3类。注意&#xff…

Vulnhub系列靶机--- Hackadmeic.RTB1

系列:Hackademic(此系列共2台) 难度:初级 信息收集 主机发现 netdiscover -r 192.168.80.0/24端口扫描 nmap -A -p- 192.168.80.143访问80端口 使用指纹识别插件查看是WordPress 根据首页显示的内容,点击target 点击…

webshell绕过

文章目录 webshell前置知识进阶绕过 webshell 前置知识 <?phpecho "A"^""; ?>运行结果 可以看到出来的结果是字符“&#xff01;”。 为什么会得到这个结果&#xff1f;是因为代码的“A”字符与“”字符产生了异或。 php中&#xff0c;两个变…