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

news2024/11/26 11:48:24

本章重点

  • 队列的概念及结构

  • 队列的实现方式

  • 链表方式实现栈接口

  • 队列面试题

一、队列的概念及结构

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出 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.coloradmin.cn/o/903649.html

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

相关文章

休息刷一下周赛

7004.判断字母缩略词&#xff08;字符串&#xff09; 思路&#xff1a;1.判断字符串个数是否跟缩略词长度相同 2.定位比较 class Solution { public:bool isAcronym(vector<string>& words, string s) {int nwords.size();if(n!s.size()) return false;for(int i0…

solidworks(3)

两个方法 sr&#xff1a;代表半径&#xff08;画图时记得换成直径&#xff09;

700. 二叉搜索树中的搜索

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

窗口看门狗

从下往上看: 1. 时钟设置 RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG,ENABLE);//使能独立看门狗时钟 WWDG_SetPrescaler(WWDG_Prescaler_8);//看门狗预分频器WWDG counter clock (PCLK1/4096)/8 2.设置窗口值 实际就是设置WWDG_CR的低七位值, 但是这个值要大于0x40(也就是…

Docker容器:docker镜像的创建及dockerfile案例

文章目录 一.docker镜像的三种创建方法1.基于现有镜像创建1.1 启动镜像1.2 生成新镜像 2.基于本地模板创建2.1 OPENVZ 下载模板2.2 导入容器生成镜像 3.基于dockerfile创建3.1 dockerfile结构及分层3.2 联合文件系统3.3 docker镜像加载原理及过程 4.dockerfile操作常用的指令4.…

16.ingress

文章目录 ingress外部访问externalIP组成Ingress-Nginx部署 nginx-ingress-controllerDaemonSetHostNetwork创建 ingress控制器创建ingress资源 DeploymentNodePort模式Ingress HTTPS 代理访问生成证书和私钥创建secret资源创建Ingress资源 Nginx 进行 BasicAuthnginx进行重写总…

18----注释

本节是markdown教程的最后一节&#xff0c;也是最简单的一节----注释。 我们常见的注释可能是这样的&#xff1a; 也可能是这样的&#xff1a; 注释无处不在&#xff0c;就让我们抱着一丝期待&#xff0c;来看看markdown的注释吧&#xff01; 一、基本用法&#xff1a; Ma…

深入AQS原理(我在一开始学的时候就把非公平锁和公平锁给弄混了)

谈到并发&#xff0c;我们不得不说AQS(AbstractQueuedSynchronizer)&#xff0c;所谓的AQS即是抽象的队列式的同步器&#xff0c;内部定义了很多锁相关的方法&#xff0c;我们熟知的ReentrantLock、ReentrantReadWriteLock、CountDownLatch、Semaphore等都是基于AQS来实现的。 …

13.实现业务功能--板块信息

目录 获取在首页中显示的版块 1. 实现逻辑 2. 创建扩展 Mapper.xml 3. 修改 DAO 4. 创建 Service 接口 5. 实现 Service 接口 6. 生成测试方法 7. 实现 Controller 8. 实现前端页面 在数据库中执行以下 SQL 语句&#xff1a; INSERT INTO t_board (id, name, article…

Dockerfile制作Web应用系统nginx镜像

目录 1.所需实现的具体内容 2.编写Dockerfile Dockerfile文件内容&#xff1a; 默认网页内容&#xff1a; 3.构建镜像 4.现在我们运行一个容器&#xff0c;查看我们的网页是否可访问 5.现在再将我们的镜像打包并上传到镜像仓库 1.所需实现的具体内容 基于centos基础镜像…

GAN:对抗生成网络,前向传播和后巷传播的区别

目录 GAN&#xff1a;对抗生成网络 损失函数 判别器开始波动很大&#xff0c;先调整判别器 生成样本和真实样本的统一&#xff1a;真假难辨​编辑 文字专图片​编辑 头像转表情包​编辑 头像转3D​编辑 后向传播 1. 前向传播&#xff08;forward&#xff09; 2. 反向传播&…

自我管理篇--“90%的简历会被刷掉”这个现象背后的原因

以上简历模板资源的排版可能不是最优&#xff0c;但工作经历可以借鉴 文章目录 一、简历问题出在什么地方二、如何提升简历的质量三、如何避免常见的简历错误四、如何让你的简历脱颖而出五、如何准备面试 为什么90%的简历会被淘汰 在当今竞争激烈的就业市场中&#xff0c;求职者…

OCR扫描仪应该怎么选?

选择OCR扫描仪时&#xff0c;以下几个因素需要考虑&#xff1a; 1. 扫描质量&#xff1a;确保选购的OCR扫描仪能够提供高质量的扫描结果。关注分辨率&#xff08;通常以dpi表示&#xff09;&#xff0c;辨识度和颜色深度等技术指标&#xff0c;以满足您的需求。 2. 扫描速度&a…

电脑报错vcomp100.dll丢失怎样修复?这三个方法可以解决

vcomp100.dll是微软Visual C 2005 Redistributable Package的一部分&#xff0c;它包含了运行某些程序所需的C运行时库。当电脑中的vcomp100.dll文件丢失或损坏时&#xff0c;可能会导致一些程序无法正常运行&#xff0c;甚至出现系统崩溃等问题。 那么&#xff0c;当遇到这样的…

openpnp - 自动换刀的设置

文章目录 openpnp - 自动换刀的设置概述笔记采用的openpnp版本自动换刀库的类型选择自动换刀设置前的注意事项先卸掉吸嘴座上所有的吸嘴删掉所有的吸嘴设置自动换刀的视觉识别设置吸嘴座为自动换刀 - 以N1为例备注补充 - 吸嘴轴差个0.3mm, 就有可能怼坏吸嘴END openpnp - 自动换…

Laravel 框架构造器的排序分组.子查询 JOIN 查询 构造器的增删改 ⑦

作者 : SYFStrive 博客首页 : HomePage &#x1f4dc;&#xff1a; THINK PHP &#x1f4cc;&#xff1a;个人社区&#xff08;欢迎大佬们加入&#xff09; &#x1f449;&#xff1a;社区链接&#x1f517; &#x1f4cc;&#xff1a;觉得文章不错可以点点关注 &#x1f44…

go1.21.0.windows-amd64.msi

go1.21.0.windows-amd64.msi Windows 10 or greater required.

漏洞指呗-VluFocus靶场专栏-番外篇

漏洞指呗-VluFocus靶场专栏-番外篇奇技淫巧 &#x1f338;struts2漏洞扫描工具&#x1f338;step1 修改ip和端口step2 验证漏洞是否存在step3 执行cmd命令&#xff0c;获取flag &#x1f338;Goby插件工具headshot&#x1f338;step1 输入ip和端口 检测step2 cmd 输入指令 &…

LeetCode 833. Find And Replace in String【字符串,哈希表,模拟】1460

本文属于「征服LeetCode」系列文章之一&#xff0c;这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁&#xff0c;本系列将至少持续到刷完所有无锁题之日为止&#xff1b;由于LeetCode还在不断地创建新题&#xff0c;本系列的终止日期可能是永远。在这一系列刷题文章…

stm32单片机开关控制LED灯(中断方式)(proteus电路图)

注意了&#xff1a;一般人都是用按键button实现这个功能&#xff0c;但是我就是喜欢用Switch&#xff0c;然后我就用了Switch&#xff0c;喜欢的朋友欢迎看一看 不同地方在于&#xff1a;这里是interrupt 函数 void EXTI0_IRQHandler(void) {/* USER CODE BEGIN EXTI0_IRQn 0…