栈和队列(2)

目录

🍁一、队列的概念

🍁二、针对本文章给出的几点注意事项:

🍁三、队列的存储结构

🌕(一)、队列的顺序存储结构

⭐️ 循环队列的介绍:

⭐️ 循环队列的入队操作:

⭐️ 循环队列的出队操作:

⭐️ 判断队满的约定:

🌕(二)、队列的链式存储

🍁四、队列的实现

🌕(一)、代码定义

注意:

🌕(二)、初始化

🌕(三)、入队

🌕(四)、出队

🌕(五)、取队头元素

🌕(六)、取队尾元素

🌕(七)、判空

🌕(八)、获取队列元素个数

🌕(九)、销毁

🌕(十)、遍历

🍁、队列实现源代码

🌕(一)、main.c

🌕(二)、Queue.c

🌕(三)、Queue.h




🍁一、队列的概念

①:队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表。
②:队列具有 先进先出(简称FIFO (First In First Out))的特点
③:入队列:进行插入操作的一端称为 队尾
       出队列:进行删除操作的一端称为 队头
④:常见使用场景:

🍁二、针对本文章给出的几点注意事项:

(一)、队列使用单向队列即可,因为双向体现不了它的优势,而且双向还会多一个指针域,内存能省一点是一点

(二)、哨兵位(带头)可要可不要,因为哨兵一般解决结构体二级指针,而本次小编会以另外一种方法解决二级指针的问题

(三)、队列可以用数组实现,也可以用链表来实现,但优先选择链表,对于队列,链表的优势远大于数组,因为数组入队很简单,但出队就涉及数据的覆盖,比较麻烦;

(四)、在链表的头部进行出队,尾部进行入队。

(五)、队列实现的具体步骤我会放在代码注释里面,大家请注意一下!因为小编找不到合适的方式,有推荐的小伙伴可以评论区给小编一些建议。

🍁三、队列的存储结构

🌕(一)、队列的顺序存储结构

1.队列的顺序存储简称“顺序队列”;

2.它由一个存放队列元素的一维数组,以及分别指示队头和队尾的“箭头”所组成;

3.通常约定:队尾箭头指示队尾元素在一维数组的当前位置;队头箭头指示队头元素在一维数组中当前位置的前一个位置;

4.代码定义:

#define maxsize 10//队列可能的最大长度
typedef int elemtype;
typedef struct
{
    elemtype elem[maxsize];
    int front;//队头箭头
    int rear;//队尾箭头
}squeuetp;

假设Sq为squeuetp变量,即Sq表示一个顺序队列,则入栈操作为:

Sq.rear=Sq.rear+1;//修改队尾指针rear
Sq.elem[Sq.rear]=x;//将数据x放入rear所指示的位置

类似的出队列需要修改队头指针:

Sq.front=Sq.front+1

以下是出队的几种状态:

由图可见,当队空时:Sq.front==Sq.rear,但是队满的条件是什么呐?

队满条件会是:Sq.rear==maxsize吗?

答:由第三幅图可以看出,显然不是,在此图状态下顺序队列的存储空间并没有被占满,因此这是一种“假溢出”的现象。

⭐️ 循环队列的介绍:

为了克服假溢出的现象,一个巧妙的方法办法是把队列设想成为一个循环的表;

设想Sq.elem[0]接在Sq.elem[maxsize-1]位置之后。

这种存储结构称为“循环队列”

此时,利用取余运算(%),很容易实现队头。队尾指针意义下的加1操作,

⭐️ 循环队列的入队操作:

此时的入队操作变为:加1取余

Sq.rear=(Sq.rear+1)%maxsize;
Sq.elem[Sq.rear]=x;

例如如果Sq.rear指向elem[maxsize-1]的位置,那么下一次就会指向

elem[(maxsize-1+1)%maxsize]的位置,即elem[0]的位置;

这样,我们就可以循环利用每个位置的空间,解决的假溢出问题;

⭐️ 循环队列的出队操作:

出队操作变为:

Sq.front=(Sq.front+1)%maxsize;
⭐️ 判断队满的约定:

为了能准确确定队满的条件,我们约定队头指针指示的位置不用来存放元素,这样当队尾指针“绕一圈”后追上队头指针,视为队满,故此时

队满的条件为

(Sq.rear+1)%maxsize==Sq.front

队空的条件为

Sq.front==Sq.rear

队满图:

对空图:

🌕(二)、队列的链式存储

上面已经介绍过了,即用单链表拉实现队列;

并且队列的链式存储比顺序存储优势大,所以下面我们实现也是实现队列的链式存储;

🍁四、队列的实现

🌕(一)、代码定义

之前实现链表的时候我们知道,在不带头且插入第一个数据时会修改结构体指针,此时就需要使用二级指针的参与,从而分类讨论,是情况变得复杂;既然我们不带头,并且这里即需要头指针还需要尾指针,那么我们可以有如下实现:

typedef int QDatatype;
typedef struct QueueNode
{
	struct QueueNode* next;
	QDatatype data;
}QNode;

typedef struct Queue
{
	QNode* head;//头指针,指向首结点
	QNode* tail;//尾指针,指向为结点
	int size;//记录队列长度
}Que;

我们将头指针head和尾指针tail重新封装成一个结构体,这样即使是在插入第一个数据,那么我们修改head指针,相当于修改结构体Queue的成员,所以我们全部情况只需要结构体Queue的一级指针即可;

注意:

截至目前:我们已经接触了三种改变结构体指针head不需要传参数二级指针的方法:

(一)、就是使用带哨兵的head,这样改变的是head的next域,即改变结构体成员;

(二)、使用不带哨兵的head,但最后会返回新head,在函数外面用适当的指针来接收返回值;

(三)、就是上述所示,在不带哨兵的情况,将头指针、尾指针封装成一个结构体,这样改变head或tail时,改变的是该结构体的成员,只需使用该结构体的一级指针即可;

🌕(二)、初始化

//初始化
void Queueinit(Que* ps)
{
	assert(ps);
	ps->head = ps->tail = NULL;
	ps->size = 0;
}

🌕(三)、入队

①:因为我们使用的是链表结构,所以不用考虑增容;

②:但入队的时候要注意第一次入队时的区别,从而分类讨论;

③:入队后元素数量加1;

//入队
void QueuePush(Que* ps, QDatatype x)
{
	assert(ps);
	//创建一个新结点
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc");
		return;
	}
	//因为是在尾结点入队,所以入队之后结点next域要置空
	newnode->data = x;
	newnode->next = NULL;
	//第一次插入是结构体指针之间的赋值,之后才是结构体成员的赋值,所以要分情况
	//记住tail指针始终指向尾结点,所以入队之后要对tail指针重定位
	if (ps->tail == NULL)
	{
		ps->head = ps->tail = newnode;
	}
	else
	{
		ps->tail->next = newnode;
		ps->tail = newnode;
	}
	//入队后元素数量+1
	ps->size++;
}

🌕(四)、出队

出队的时候也要注意几点事项:

①:出队前,一定要检查队列是否为空;

②:因为队列涉及头指针head和尾指针tail,所以当出队列的最后一个元素后,head和tail指针都要指向NULL,所以我们可以分类讨论以防万一;

③:出队后元素数量size减1

//出队
void QueuePop(Que* ps)
{
	assert(ps);
	//检查队列是否为空,若为空则assert函数报错提示
	assert(!QueueEmpty(ps));
	//队列不为空,进行尾删
	//当对列只剩下一个元素时,要注意head和tail指针都要指向NULL,所以为了安全起见,进行分类讨论
	if (ps->head->next == NULL)
	{
		free(ps->head);
		//注意free释放的是该指针指向的空间,而不是释掉该指针
		ps->head = ps->tail = NULL;
	}
	else
	{
		QNode* next = ps->head->next;
		free(ps->head);
		ps->head = next;
	}
	//出队列,元素数量-1
	ps->size--;
}

🌕(五)、取队头元素

操作很简单,只需返回head结点的data域;

但要注意,首先要检查队列是否为空:

//取队头
QDatatype QueueFront(Que* ps)
{
	assert(ps);
	//检查队列为不为空
	assert(!QueueEmpty(ps));
	//返回首结点的data域
	return ps->head->data;
}

🌕(六)、取队尾元素

操作和(五)类似:

//取队尾
QDatatype QueueBack(Que* ps)
{
	assert(ps);
	//检查队列为不为空
	assert(!QueueEmpty(ps));
	//返回尾结点的data域
	return ps->tail->data;
}

🌕(七)、判空

规则:

队列为空返回真;

队列不为空返回假;

我们用一个表达式的逻辑值来实现,如下:

//判空
bool QueueEmpty(Que* ps)
{
	assert(ps);
	//为空返回真,不为空返回假
	return (ps->head == NULL);
}

🌕(八)、获取队列元素个数

操作很简单,直接返回size即可:


//获取队列元素个数
int QueueSize(Que* ps)
{
	assert(ps);
	return ps->size;
}

🌕(九)、销毁

与单链表的销毁类似,一个结点一个结点的销毁;

先用临时指针cur指向head,再用临时指针next保存cur的下一个结点,再释放当前结点cur,然后cur重定位到下一个结点,直至cur为NULL;

//销毁
void QueueDestroy(Que* ps)
{
	assert(ps);
	QNode* cur = ps->head;
	//先保存下一个结点,在释放当前结点,在重定位
	while (cur)
	{
		QNode* Qnext = cur->next;
		free(cur);
		cur = Qnext;
	}
	ps->head = ps->tail = NULL;
	ps->size = 0;
}

🌕(十)、遍历

与栈类似的,队列遍历的时候也不用封装成函数,而是用循环,边遍历边出队,遍历完后,队列为空,这与其后面使用场景有着密切联系;

void Queuetest1()
{
	Que ps;
	Queueinit(&ps);
	QueuePush(&ps, 1);
	QueuePush(&ps, 2);
	QueuePush(&ps, 3);
	QueuePush(&ps, 4);
	QueuePush(&ps, 5);

	//遍历
	while (!QueueEmpty(&ps))
	{
		//当前数据是整形,所以占位符用%d
		printf("%d ", QueueFront(&ps));
		QueuePop(&ps);
	}
	printf("\n");
	QueueDestroy(&ps);
}

先进先出,符合其特征;

🍁、队列实现源代码

🌕(一)、main.c


#include"Queue.h"

void Queuetest1()
{
	Que ps;
	Queueinit(&ps);
	QueuePush(&ps, 1);
	QueuePush(&ps, 2);
	QueuePush(&ps, 3);
	QueuePush(&ps, 4);
	QueuePush(&ps, 5);

	//遍历
	while (!QueueEmpty(&ps))
	{
		//当前数据是整形,所以占位符用%d
		printf("%d ", QueueFront(&ps));
		QueuePop(&ps);
	}
	printf("\n");
	QueueDestroy(&ps);
}


int main()
{
	Queuetest1();
	return 0;
}

🌕(二)、Queue.c

#include"Queue.h"

//初始化
void Queueinit(Que* ps)
{
	assert(ps);
	ps->head = ps->tail = NULL;
	ps->size = 0;
}

//销毁
void QueueDestroy(Que* ps)
{
	assert(ps);
	QNode* cur = ps->head;
	//先保存下一个结点,在释放当前结点,在重定位
	while (cur)
	{
		QNode* Qnext = cur->next;
		free(cur);
		cur = Qnext;
	}
	ps->head = ps->tail = NULL;
	ps->size = 0;
}

//入队
void QueuePush(Que* ps, QDatatype x)
{
	assert(ps);
	//创建一个新结点
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc");
		return;
	}
	//因为是在尾结点入队,所以入队之后结点next域要置空
	newnode->data = x;
	newnode->next = NULL;
	//第一次插入是结构体指针之间的赋值,之后才是结构体成员的赋值,所以要分情况
	//记住tail指针始终指向尾结点,所以入队之后要对tail指针重定位
	if (ps->tail == NULL)
	{
		ps->head = ps->tail = newnode;
	}
	else
	{
		ps->tail->next = newnode;
		ps->tail = newnode;
	}
	//入队后元素数量+1
	ps->size++;
}

//出队
void QueuePop(Que* ps)
{
	assert(ps);
	//检查队列是否为空,若为空则assert函数报错提示
	assert(!QueueEmpty(ps));
	//队列不为空,进行尾删
	//当对列只剩下一个元素时,要注意head和tail指针都要指向NULL,所以为了安全起见,进行分类讨论
	if (ps->head->next == NULL)
	{
		free(ps->head);
		//注意free释放的是该指针指向的空间,而不是释掉该指针
		ps->head = ps->tail = NULL;
	}
	else
	{
		QNode* next = ps->head->next;
		free(ps->head);
		ps->head = next;
	}
	//出队列,元素数量-1
	ps->size--;
}

//取队头
QDatatype QueueFront(Que* ps)
{
	assert(ps);
	//检查队列为不为空
	assert(!QueueEmpty(ps));
	//返回首结点的data域
	return ps->head->data;
}

//取队尾
QDatatype QueueBack(Que* ps)
{
	assert(ps);
	//检查队列为不为空
	assert(!QueueEmpty(ps));
	//返回尾结点的data域
	return ps->tail->data;
}


//判空
bool QueueEmpty(Que* ps)
{
	assert(ps);
	//为空返回真,不为空返回假
	return (ps->head == NULL);
}

//获取队列元素个数
int QueueSize(Que* ps)
{
	assert(ps);
	return ps->size;
}

🌕(三)、Queue.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
#include<stdbool.h>
#include<stdlib.h>

typedef int QDatatype;
typedef struct QueueNode
{
	struct QueueNode* next;
	QDatatype data;
}QNode;

typedef struct Queue
{
	QNode* head;//头指针,指向首结点
	QNode* tail;//尾指针,指向为结点
	int size;//记录队列长度
}Que;

//初始化
void Queueinit(Que* ps);

//销毁
void QueueDestroy(Que* ps);

//入队
void QueuePush(Que* ps,QDatatype x);

//出队
void QueuePop(Que *ps);

//取队头
QDatatype QueueFront(Que* ps);

//取队尾
QDatatype QueueBack(Que* ps);

//判空
bool QueueEmpty(Que* ps);

//获取队列元素个数
int QueueSize(Que* ps);

本次知识到此结束,希望对你有所帮助

评论 46
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

成工小白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值