【数据结构】栈和队列(队列篇)

news2024/11/17 17:39:23

上期我们已经学习了数据结构中的栈,这期我们开始学习队列。

目录

1.队列的概念及结构

2.队列的实现

队列结构体定义

常用接口函数

初始化队列

队尾入队列

队头出队列

获取队列头部元素、

获取队列队尾元素

获取队列中有效元素个数

检测队列是否为空

销毁队列

3.循环队列


1.队列的概念及结构

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

 从网上找来的队列结构示意图:

2.队列的实现

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

队列结构体定义

我们使用的是不带头节点的单向链表来实现队列,而队列要在队尾插入数据,因此每次都要遍历链表找队尾,这样会使得程序的运行效率变低。这里采取了一种解决办法:定义一个指向队尾的指针tail 每次插入新的数据就只要将tail中的next指针指向新节点即可,同时插入新的数据后再更新tail的位置。

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

这里单独定义了一个结构体来存储头指针head和尾指针tail

typedef struct Queue
{
	QNode* head;
	QNode* tail;
}Queue;

常用接口函数

//初始化
void QueueInit(Queue* pq);
//插入数据
void QueuePush(Queue* pq, QDataType x);
//销毁
void QueueDestroy(Queue* pq);
//弹出
void QueuePop(Queue* pq);
//取队头数据
QDataType QueueFront(Queue* pq);
//取队尾数据
QDataType QueueBack(Queue* pq);
//判断是否为空
bool QueueEmpty(Queue* pq);
//计算大小
int QueueSize(Queue* pq);

初始化队列

由于这里使用的是不带头节点的单向链表,所以初始化队列只需要将头指针和尾指针都指向空即可。如果是要定义一个带头节点的链表,则需要在这里创建一个头节点

//初始化
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->head = pq->tail = NULL;
}

队尾入队列

由于我们已经将链表的尾部给记录了下来,所以不需要遍历链表,只要尾部节点中的next指针指向新节点

 同时需要判断链表是否为空,如果链表是第一次插入数据,需要将头指针和尾指针都指向新节点。

//队尾插入数据
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	//创建新节点
	QNode* newNode = (QNode*)malloc(sizeof(QNode));
	if (newNode == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	newNode->data = x;
	newNode->next = NULL;
	
	//将新节点链接起来
	if (pq->tail == NULL)
	{
		pq->head = pq->tail = newNode;
	}
	else
	{
		pq->tail->next = newNode;
		pq->tail = newNode;
	}
}

队头出队列

在弹出数据之前,要判断链表是否为空,这里可以用assert断言:

接下来是具体的弹出操作:

  • 判断队列是否只有一个节点。如果是,表示队列中只剩下一个节点,直接释放这个节点,同时将头指针和尾指针都置为空。
  • 如果队列中有多个节点,将头指针pq->head指向下一个节点,然后释放原来的头节点。这样就完成了一个节点的弹出操作。
//弹出
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	if (pq->head->next == NULL)
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	else
	{
		QNode* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}
}

获取队列头部元素、

返回队列头节点的数据 pq->head->data 即可。

//取队头数据
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	return pq->head->data;
}

获取队列队尾元素

  • 返回队列尾节点的数据 pq->tail->data 即可。
//取队尾数据
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	return pq->tail->data;
}

获取队列中有效元素个数

计算大小有多种方法,常用的两种是:

一种是在定义Queue结构体时增加一个用于计数的变量,每次插入数据就自加一下,弹出数据就自减一下。

typedef struct Queue
{
	int size;
	QNode* head;
	QNode* tail;
}Queue;

另外一种是遍历链表,这种方法效率比较低,当然,如果你不在乎这点效率问题,你可以使用这种方法,我们这里也是使用这种方式:

//计算大小
int QueueSize(Queue* pq)
{
	assert(pq);
	QNode* cur = pq->head;
	int size = 0;

	while (cur)
	{
		++size;
		cur = cur->next;
	}

	return size;
}

检测队列是否为空

如果头节点head为空,则队列为空。

//判断是否为空
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->head == NULL;
} 

销毁队列

销毁队列和销毁普通链表一样:

  • 使用一个指针cur指向队列的头节点pq->head
  • 在一个循环中,遍历队列的所有节点,直到cur为空。
  • 循环结束后,将队列的头指针和尾指针都置为空,表示队列已经被销毁。
//销毁
void QueueDestroy(Queue* pq)
{
	assert(pq);
	QNode* cur = pq->head;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}

	pq->head = pq->tail = NULL;
}

3.循环队列

循环队列的概念

实际中我们有时还会使用一种队列叫循环队列。如操作系统课程讲解生产者消费者模型 时可以就会使用循环队列。环形队列可以使用数组实现,也可以使用循环链表实现。

循环队列是一种在固定大小的数组或链表上实现的队列,它可以通过循环利用数组或链表中的空间来实现入队和出队操作。当队列满时,新的元素会从开头重新进入队列,实现循环利用。                                                                                                

通常循环队列有两个指针,一个是头指针head,另一个是尾指针tail, 当head和tail指向同一个节点时,表示队列为空。

当从队尾插入一个数据时,tail就会向后移动。当弹出队头数据时,head向后移动。

空的循环队列:

插入3个数据:

 弹出一个队首的数据:

按照上面这种情况,当队列已经满了的时候,head和tail也会指向同一个节点。这样我们就无法区分队列是空还是满了。因此想出了这两种方法:

方案一:设置一个变量size计算队列元素个数,如果size与队列容量相等时,说明队列已满。

方案二:多开一个空间,不存储数据。  当 tail->next==head 时,表示已经满了。

一般方案二的实用性较强。

 用方案二这种方法我们会发现,如果使用链表的形式实现队列,由于tail每次都是指向尾节点的下一个节点,当我们要取尾节点的数据时,不是很方便。

因此,我们可以使用数组来实现队列。

用数组实现循环队列

原题链接:力扣

 

typedef struct 
{
    int* a;
    int k;
    int head;
    int tail;
} MyCircularQueue;


MyCircularQueue* myCircularQueueCreate(int k) 
{
    MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    //带有一个不存储数据的头
    obj->a = (int*)malloc(sizeof(int)*(k+1));
    obj->k = k;
    obj->head = obj->tail = 0;

    return obj;
}

bool myCircularQueueIsEmpty(MyCircularQueue* obj) 
{
    return obj->head == obj->tail;
}

bool myCircularQueueIsFull(MyCircularQueue* obj) 
{
    int next = obj->tail + 1;
    if(obj->tail == obj->k)
    {
        next = 0;
    }

    return next == obj->head;
}

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) 
{
    if(myCircularQueueIsFull(obj))
    {
        return false;
    }

    obj->a[obj->tail] = value;
    obj->tail++;

    //tail到达数组临界,调整tail位置到开头,以达到循环队列效果
    if(obj->tail == obj->k + 1)
    {
        obj->tail = 0;
    }

    return true;
}

bool myCircularQueueDeQueue(MyCircularQueue* obj) 
{
    if(myCircularQueueIsEmpty(obj))
    {
        return false;
    }

    obj->head++;

    if(obj->head == obj->k + 1)
    {
        obj->head = 0;
    }

    return true;
}

int myCircularQueueFront(MyCircularQueue* obj) 
{
    if(myCircularQueueIsEmpty(obj))
    {
        return -1;
    }

    return obj->a[obj->head];
}

int myCircularQueueRear(MyCircularQueue* obj) 
{
    if(myCircularQueueIsEmpty(obj))
    {
        return -1;
    }

    int prev = obj->tail - 1;
    if(obj->tail == 0)
    {
        prev = obj->k;
    }

    return obj->a[prev];
}

void myCircularQueueFree(MyCircularQueue* obj) 
{
    free(obj->a);
    free(obj);
}

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

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

相关文章

chatgpt赋能python:用Python轻松给手机用户发送短信——优秀的工具在手,无限可能!

用Python轻松给手机用户发送短信——优秀的工具在手,无限可能! 作为一个有10年Python编程经验的工程师,我想分享一下如何用Python给手机用户发送短信。Python是目前非常流行的编程语言之一,它可以轻松地完成很多任务。而给用户发…

13.定时器中断

1.通用定时器工作过程: 2.时钟选择: 内部时钟(CK_INT);外部时钟模式1:外部输入脚(TIx);外部时钟模式2:外部触发输入(ETR);内部触发输入(ITRx):使用一个定时器作为另一个定时器的预分频器,例如可…

【Kafka面试题1】Kafka消费者是pull(拉)还是push(推)模式,这种模式有什么好处?

Kafka消费者是pull(拉)还是push(推)模式,这种模式有什么好处? 一、概述回答 Kafka中的Producer和consumer采用的是push-and-pull模式,即Producer只管向broker push消息,consumer只管从broker pull消息,两者对消息的生…

从零用自己数据跑R3LIVE

1、相机内参标定 相机选用4mm的广角相机,相机内参标定选择用最常见的棋盘格方法,首先安装ROS自带的包 sudo apt install ros-melodic-camera-calibration 用usb_cam启动相机后进行标定 。 rosrun camera_calibration cameracalibrator.py --size 8x6…

【Linux】网络编程相关概念介绍、UDP套接字简单演示、最简单的UDP公共聊天室实现~

文章目录 [toc] 网络编程 - 套接字一些概念1. 源ip地址与目的ip地址2. 端口号 和 socket套接字 **问题 3. 源端口号和目的端口号4. 认识TCP协议基本特点5. 认识UDP协议基本特点6. 网络字节序 socket编程接口**struct sockaddr**接口演示: 简单的UDP网络通信int socket()UDP网络…

读书笔记-《ON JAVA 中文版》-摘要18[第十八章 字符串-1]

文章目录 第十八章 字符串1. 字符串的不可变2. 的重载与 StringBuilder3. 意外递归4. 字符串操作5. 格式化输出5.1 printf()5.2 System.out.format()5.3 Formatter 类5.3.1 格式化修饰符5.3.2 Formatter 转换 5.4 String.format() 6. 自我学习总结 第十八章 字符串 字符串操作…

【专题速递】更多的解决方案:传统行业不再「传统」

// 音视频技术作为企业数字化转型的关键技术与能力之一,为众多传统行业在生产、服务、管理与维护等方面提供了强有力的支持。那么,音视频技术是如何助力企业数字化转型的?7月28日LiveVideoStackCon上海站数字化与行业案例专场,为…

【AI】PyTorch安装记录及Anaconda环境配置

【AI】PyTorch安装记录及Anaconda环境配置 说下本地环境,RTX4070 12GB GPU;618刚买,不能让他闲着,配置一下炼丹环境,开始为打工人工作。为了方便后续部署模型之间依赖不冲突,所以使用Anaconda管理Python环…

【数据结构】第 1~10 章:思维导图与重点汇总

目录 一、概论 (1)思维导图 (2)常见名词 (3)数据结构的定义 (4)抽象数据类型 ADT (5)算法 (6)评价算法的好坏的因素 &am…

Python基础 —— 循环语句

如约来更新循环语句了.说到循环,有一定编程基础的小伙伴们都知道,我们最常用的循环莫过于 while循环,for循环和goto循环(不过goto也不怎么常用),所以今天就来说一说 while循环和 for循环 来看一下本文大致…

IMX6ULL系统移植篇-uboot基础命令

一. uboot 启动 当设备上电启动时,需要马上按下回车键,开发板启动会停止在 uboot的启动Log信息时刻。 这就是 uboot的命令模式,即可以输入 uboot命令进行一些操作。 二. uboot 基础命令 1. help 命令 当开发板上电启动后,马…

电脑如何设置外网内网一起使用

如果你的电脑支持连接无线网,就可以设置内网外网一起使用。一般情况下,连接无线网还是网线都是系统自动链接的,但有时候开发中需要内网外网一块使用,不用手动切换网络。 首先确保我们的电脑有双网卡,可以两个都是有线网卡&#xf…

【机械臂视觉抓取从理论到实战】

1. 概述 GR-CNN:https://paperswithcode.com/paper/antipodal-robotic-grasping-using-generative 2. 环境搭建及模型训练 GR-CNN:https://github.com/skumra/robotic-grasping 下载源码创建环境 #下载robotic-grasping源码 git clone https://github.…

CVE-2021-3493:Overlay 文件系统 Ubuntu 本地提权漏洞分析

分析此漏洞的文章非常多,在此只是记录一下复现漏洞的过程以及对漏洞的个人理解。Linux 内核漏洞有一定的准入门槛,不适合小白阅读。 基本信息 [影响范围] Ubuntu 14.04 ~20.10 [漏洞描述] Ubuntu 内核代码允许低权限用户在使用 unshare() 函数创建的…

李彦宏:AI原生应用比大模型数量更重要

6月26日,百度创始人、董事长兼首席执行官李彦宏出席“世界互联网大会数字文明尼山对话”,发表了题为 《大模型重塑数字世界》 的演讲。 大模型是当下全球科技创新的焦点,也是全球人工智能竞赛的主战场。李彦宏认为,“新的国际竞争…

箱线图概念和使用介绍

箱线图时一种针对连续型变量的统计图。通常用作比较。 箱子中间的一条线,是数据的中位数,代表了数据的平均水平。 箱子的上限和下限,分别是数据的上四分位数和下四分位数,意味着箱子包含50%的数据。因此,箱子的高度在…

23.RocketMQ之NameServer处理Broker心跳包,更新本地路由信息

NameServer处理Broker心跳包,更新本地路由信息 DefaultRequestProcessor继承自NettyRequestProcessor:处理各种客户端的请求,如果请求类型是为REGISTER_BROKER,则将请求转发到RouteInfoManager#regiesterBroker,主要是服务器端 或者客户端或者broker发送…

go语言环境安装

文章目录 环境介绍安装软件包步骤环境变量设置来一个经典的hello worldNice 最近的项目需要用到go来开发了,前几天就已经在看书了,今天是个周末,先在家里的机器上把环境搭好,特此记录一下。 环境介绍 下载地址:https:…

RRT 算法研究(附 Python / C++ 实现)

RRT 算法研究 参考 机器人路径规划、轨迹优化课程-第五讲-RRT算法原理和代码讲解 机器人路径规划之RRT算法(附C源码) RRT算法(快速拓展随机树)的Python实现 《基于改进RRT算法的路径规划研究》 《面向室内复杂场景的移动机器人快速路径规划算法研究》 理论基础 RRT&#xff0…

meb stm32开发

matlab1028b以上 stm32cubemx5.6.0以上 stm32-mat/target 教程与代码分享 - 知乎 安装好这些后,打开matlab,打开路径STM32-MAT\STM32 打开simulink,view-lib 可以看到 在STM32CUBEMX完成底层配置,生成ioc文件