【数据结构】栈和队列(链表模拟队列)

news2024/12/28 18:07:18

 


学习本章节必须具备 单链表的前置知识,

建议提前学习:点击链接学习:单链表各种功能函数 细节 详解

本章节是学习用 单链表模拟队列

1. 单链表实现队列 思路如下

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


1.1 使用 数组 还是 链表 模拟 队列 结构?

因为需要 模拟队列 先进先出的 特性:队头 只能出,队尾 只能进

若 使用 数组模拟,每次 pop 队头操作,就需要 全部元素向前面移动,时间复杂度为 O(n)

综上,因为需要 考虑位置变化,选择 链表 实现 队列 较优


1.2. 需要使用 什么类型的 链表模拟队列?

单向

带头 / 不带头 都可以 :因为哨兵位主要用于 双向链表 找尾 ,为了方便删除,这里差别不大

不循环

我们下面实现的 是 单向不带头不循环链表

实际上,单向或双向,循环或不循环,带头或不带头 完全取决于 你自己要实现一个功能的需求,不是说 一定要固定使用 哪一个套 ,需要灵活选择使用


1.3. 单向链表 实现队列 的链表节点结构体创建:

typedef int QDataType;
typedef struct QueueNode
{
	QDataType value;            // 节点数据
	struct QueueNode* next;     // 指向下一个节点
}QNode;

1.4. 考虑效率,创建 头尾指针结构体

因为 队列需要:队头 push,队尾 pop

涉及到对 链表 的 尾部操作必须意识到:需要先进行 找尾操作,时间复杂度为 O(n)

方案:因为涉及头尾频繁操作:可以 直接 同时定义 头指针 phead 和 尾指针 ptail

技巧:同类型的变量可以封装成一个结构体

因为 phead 和 ptail 是可以不断变化的,每个相关函数都需要同时传递 phead 和 ptail 两个变量

则可以将多个同类型的变量 封装成 一个结构体,方便操作

这样,传递变量时 直接传递一个 结构体的指针就行了

typedef struct Queue
{
    QNode* phead;
    QNode* ptail;
}Queue;
// 区别:减少了传递变量的数量,利于协助开发
// void QueuePush(QNode* phead, QNode* ptail);
void QueuePush(Queue* pq);
// void QueuePop(QNode* phead, QNode* ptail);
void QueuePop(Queue* pq);


1.5. Push / Pop :入队 和 出队操作

Push 在队尾入队,Pop 在队头出队

void QueuePop(Queue* pq)
{
	assert(pq);
	// pop 的删除操作 需要分类讨论:链表节点个数为 0、为 1、为 两个以上
    
	// 为 0 :直接判空,退出操作:phead == ptail == NULL
	assert(pq->phead);    // 头节点为空 就一定代表为空了
    
	// 为 1:phead == ptail  但是 phead != NULL 的情况:即一定指向一个节点
	if (pq->phead == pq->ptail && pq->phead != NULL) {
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
	}
	else // 为 两个以上:先记录第二个节点,free 掉头节点,更新头节点
	{
		QNode* tmp = pq->phead->next;
		free(pq->phead);
		pq->phead = tmp;
	}
}

为什么 ” 头节点为空 或 尾节点为空 就一定代表链表为空了 “?


1.6. 观察上面代码:有需要 判断链表节点数量的 需求,为了简化代码与优化过程,可以 直接定义一个 size ,放进结构体中,时刻记录 链表节点数量

// 结构体更改:
typedef struct Queue
{
	QNode* phead;
	QNode* ptail;
	int size;
}Queue;

// 加入 size 后 的 Push 和 Pop 函数
void QueuePop(Queue* pq)
{
    assert(pq);
    assert(pq->phead);

    if (pq->size == 1) {
        free(pq->phead);
        pq->phead = pq->ptail = NULL;
    }
    else if (pq->size >= 2)
    {
        QNode* next = pq->phead->next;  // 保留下一个
        free(pq->phead);
        pq->phead = next;
    }
    pq->size--;    // 注意 pop 代表弹出一个节点,数量 - 1
}


void QueuePush(Queue* pq, QDataType x)
{
    assert(pq);
    // push 前先创建一个新节点
    QNode* newNode = (QNode*)malloc(sizeof(QNode));
    if (newNode == NULL) {
        perror("malloc fail");
        return;
    }
    newNode->value = x;
    newNode->next = NULL;

    
    if (pq->ptail) // 若 ptail != NULL 说明此时链表不为空
    {
        pq->ptail->next = newNode; // 旧的尾节点和一个新的点 进行链接
        pq->ptail = newNode; // 重新更新尾节点
    }
    else  // 若链表为空,则 phead 和 ptail 都要 处理
    {
        pq->phead = pq->ptail = newNode;
    }
    pq->size++;   // 数量++
}


2. 综上所述,最终代码:

Queue.c

#include"Queue.h"

// Push 入队,Pop 出队
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(pq->phead);

	if (pq->size == 1) {
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
	}
	else if (pq->size >= 2)
	{
		QNode* next = pq->phead->next;  // 保留下一个
		free(pq->phead);
		pq->phead = next;
	}
	pq->size--;    // 注意 pop 代表弹出一个节点,数量 - 1
}

void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	// push 前先创建一个新节点
	QNode* newNode = (QNode*)malloc(sizeof(QNode));
	if (newNode == NULL) {
		perror("malloc fail");
		return;
	}
	newNode->value = x;
	newNode->next = NULL;

	if (pq->ptail) // 若 ptail != NULL 说明此时链表不为空
	{
		pq->ptail->next = newNode; // 旧的尾节点和一个新的点 进行链接
		pq->ptail = newNode; // 重新更新尾节点
	}
	else  // 若链表为空,则 phead 和 ptail 都要 处理
	{
		pq->phead = pq->ptail = newNode;
	}
	pq->size++;   // 数量++
}

// 初始化
void  QueueInit(Queue* pq)
{
	assert(pq);
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}

// 销毁链表:就是 单链表的 销毁操作
void QueueDestory(Queue* pq)
{
	assert(pq);
	QNode* cur = pq->phead;
	while (cur) {
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->phead = pq->ptail = NULL;  // 最后别忘了头尾指针置为 NULL
	pq->size = 0;
}

// Front 返回队头元素
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(pq->phead); // 若链表为空 自然没有头节点;
	return pq->phead->value;
}

// Back 返回队尾元素
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(pq->ptail); // 若链表为空 自然没有尾节点;
	return pq->ptail->value;
}

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

// Size 返回节点数量
int QueueSize(Queue* pq)
{
	assert(pq);
	return pq->size;
}

Queue.h

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


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

typedef struct Queue
{
	QNode* phead;
	QNode* ptail;
	int size;
}Queue;


// 初始化
void  QueueInit(Queue* pq);   

// Push 入队,Pop 出队
void QueuePush(Queue* pq, QDataType x);
void QueuePop(Queue* pq);

// Front 队头元素,Back 队尾元素
QDataType QueueFront(Queue* pq);
QDataType QueueBack(Queue* pq);

// Empty 判断是否为空,Size 返回节点数量
bool QueueEmpty(Queue* pq);
int QueueSize(Queue* pq);

// 销毁链表
void QueueDestory(Queue* pq);

Main.c

#include"Queue.h"

int main()
{
	Queue q;   // 创建队列结构体
	QueueInit(&q); // 初始化:用于初始化链表的头尾节点:phead  /  ptail

	for (int i = 1; i <= 5; ++i) {  // 入队列 几个元素: 1 2 3 4 5
		QueuePush(&q, i); 
	}
	
	// 一个个读取队列元素
	while (!QueueEmpty(&q))
	{
		printf("%d ", QueueFront(&q));
		QueuePop(&q);
	}

	QueueDestory(&q);
	return 0;
}

3. LeetCode:225.队列实现栈

使用两个队列实现栈

核心思路:

保持一个队列存数据,一个队列为空
push 入数据,入到不为空的队列
pop 出数据,将 非空队列中 前 n-1 个数据 导入 空队列

代码实现

// 以下均是 链式队列的 相关函数,复制粘贴过来罢了
///
typedef int QDataType;
typedef struct QueueNode
{
	QDataType value;
	struct QueueNode* next;
}QNode;

typedef struct Queue
{
	QNode* phead;
	QNode* ptail;
	int size;
}Queue;

void QueuePop(Queue* pq)
{
	assert(pq);
	assert(pq->phead);

	if (pq->size == 1) {
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
	}
	else if (pq->size >= 2)
	{
		QNode* next = pq->phead->next;  // 保留下一个
		free(pq->phead);
		pq->phead = next;
	}
	pq->size--;    // 注意 pop 代表弹出一个节点,数量 - 1
}

void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	// push 前先创建一个新节点
	QNode* newNode = (QNode*)malloc(sizeof(QNode));
	if (newNode == NULL) {
		perror("malloc fail");
		return;
	}
	newNode->value = x;
	newNode->next = NULL;

	if (pq->ptail) // 若 ptail != NULL 说明此时链表不为空
	{
		pq->ptail->next = newNode; // 旧的尾节点和一个新的点 进行链接
		pq->ptail = newNode; // 重新更新尾节点
	}
	else  // 若链表为空,则 phead 和 ptail 都要 处理
	{
		pq->phead = pq->ptail = newNode;
	}
	pq->size++;   // 数量++
}

// 初始化
void  QueueInit(Queue* pq)
{
	assert(pq);
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}

// 销毁链表:就是 单链表的 销毁操作
void QueueDestory(Queue* pq)
{
	assert(pq);
	QNode* cur = pq->phead;
	while (cur) {
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->phead = pq->ptail = NULL;  // 最后别忘了头尾指针置为 NULL
	pq->size = 0;
}

// Front 返回队头元素
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(pq->phead); // 若链表为空 自然没有头节点;
	return pq->phead->value;
}

// Back 返回队尾元素
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(pq->ptail); // 若链表为空 自然没有尾节点;
	return pq->ptail->value;
}

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

// Size 返回节点数量
int QueueSize(Queue* pq)
{
	assert(pq);
	return pq->size;
}
// 以上均是 链式队列的 相关函数,复制粘贴过来罢了
///



///
// 下面是题目主体
typedef struct {
    Queue q1, q2; // 创建两个队列
} MyStack;


MyStack* myStackCreate() {
    MyStack* pst = (MyStack*)malloc(sizeof(MyStack)); // 创建一个栈
    QueueInit(&(pst->q1));
    QueueInit(&(pst->q2));
    return pst;
}

void myStackPush(MyStack* obj, int x) {
    // push 到 不为空的 队列
    if(QueueEmpty(&(obj->q1))) {
        QueuePush(&(obj->q2), x);
    }
    else {
        QueuePush(&(obj->q1), x);
    }
}

int myStackPop(MyStack* obj) {
    // 找到非空的 队列,将 size-1 个元素放进 另一个空队列,同时最后一个元素pop掉
    // 有两种情况:q1 为空,q2 不为空, q2 为空,q1 不为空
    // 可以先假设,后调整
    // 先假设 队列1 为空,队列2 不为空,后面判断后调整
    Queue* pEmptyQ = &(obj->q1);
    Queue* pNonEmptyQ = &(obj->q2);
    if(!QueueEmpty(&(obj->q1))){
        pEmptyQ = &(obj->q2);
        pNonEmptyQ = &(obj->q1);
    }

    // 将不为空队列 的前 n-1 个元素放进 空队列中
    while(QueueSize(pNonEmptyQ) > 1) {
        int x = QueueFront(pNonEmptyQ);
        QueuePush(pEmptyQ, x);
        QueuePop(pNonEmptyQ);
    }
    int t = QueueFront(pNonEmptyQ);
    QueuePop(pNonEmptyQ);
    return t;
}

int myStackTop(MyStack* obj) {
    if(QueueEmpty(&(obj->q1))) {
        return QueueBack(&(obj->q2));
    }
    else {
        return QueueBack(&(obj->q1));
    }
}

bool myStackEmpty(MyStack* obj) {
    return QueueEmpty(&(obj->q1)) && QueueEmpty(&(obj->q2)); // 两个都为空才是栈空
}

void myStackFree(MyStack* obj) {
    if(QueueEmpty(&(obj->q1))) {
        QueueDestory(&(obj->q2));
    }
    else if(QueueEmpty(&(obj->q2))) {
        QueueDestory(&(obj->q1));
    }
}


4. LeetCode:223.栈实现队列

使用 两个栈 模拟队列

思路

定义一个 pushStack :专门用来接收 push入队列的 数据
定义一个 popStack :专门用来 pop 队列数据
当 popStack 为空时,此时需要 pop 操作,则将 pushStack 的数据全部 放进 popStack ,补充数据(注意是全部);若popStack 不为空,则进行 pop 操作即可
当 需要 push 操作,直接往 pushStack 中放数据即可

演示: push 2 次,pop 1 次,push 3 次, pop 3 次

【若文章有什么错误,欢迎评论区讨论或私信指出】 

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

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

相关文章

代码中哪些复杂的结构图是怎么画出来的?

最近找到一个在线的代码架构图生成器&#xff0c;你只要画出结构图&#xff0c;就会自动生成代码示意图&#xff1a; https://asciiflow.com/#/

计算机网络—TCP协议详解:协议构成、深度解析(3)

&#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;マリンブルーの庭園—ずっと真夜中でいいのに。 0:34━━━━━━️&#x1f49f;──────── 3:34 &#x1f504; ◀️…

变频电源都有哪些故障?

变频电源是一种可以将市电的交流电转换为频率可调的交流电的电力电子设备&#xff0c;它可以根据需求调整输出电压和频率&#xff0c;为设备运行提供稳定可靠的电源。但是在实际使用过程中常会遇到一些故障&#xff0c;今天纳米软件将介绍这些故障以及解决办法。 1. 短路 短路故…

开源项目实现简单实用的股票回测

1 引言 之前&#xff0c;尝试做股票工具一直想做的大而全&#xff0c;试图抓取长期的各个维度数据&#xff0c;然后统计或者训练模型。想把每个细节做到完美&#xff0c;结果却陷入了细节之中&#xff0c;最后烂尾了。 最近&#xff0c;听到大家分享了一些关于深度学习、时序…

【xhs爬虫软件】把小红书评论comment接口封装成GUI采集工具!

用Python开发爬虫采集软件&#xff0c;可自动抓取小红书评论数据&#xff0c;并且含二级评论。 小红书的评论接口URL是&#xff1a; https://edith.xiaohongshu.com/api/sns/web/v2/comment/page 开发者模式分析过程&#xff1a; 进而封装成GUI界面软件&#xff0c;如下&…

【Axure教程0基础入门】05动态面板

05动态面板 1.动态面板是什么&#xff1f; 一个用来存放多个元件的容器&#xff08;container&#xff09; 其中包含多个状态&#xff08;state&#xff09;&#xff0c;但同时只能显示一个 状态之间&#xff0c;可以通过交互动作&#xff08;action&#xff09;控制切换和动…

【Spring Cloud】服务容错中间件Sentinel进阶——五大规则

文章目录 Sentinel的概念和功能基本概念资源规则 重要功能流量控制熔断降级系统负载保护 SentineI 规则流控规则简单配置配置流控模式直接流控模式关联流控模式链路流控模式 配置流控效果 熔断规则慢调用比例异常比例异常数 热点规则热点规则简单使用热点规则增强使用 授权规则…

请编写函数fun,该函数的功能是:移动字符串中的内容,移动的规则如下:把第1到第m个字符,平移到字符串的最后,把第m+l到最后的字符移到字符串的前部。

本文收录于专栏:算法之翼 https://blog.csdn.net/weixin_52908342/category_10943144.html 订阅后本专栏全部文章可见。 本文含有题目的题干、解题思路、解题思路、解题代码、代码解析。本文分别包含C语言、C++、Java、Python四种语言的解法完整代码和详细的解析。 题干 请编…

Ai-WB2 系列模组SDK接入亚马逊

文章目录 前言一、准备二、亚马逊云物模型建立1. 注册亚马逊账号&#xff0c;登录AWS IoT控制台&#xff0c;[注册地址](https://aws.amazon.com/cn/)2. 创建好之后点击登录3. 创建物品以及下载证书 三、连接亚马逊云demo获取以及配置1. 下载源码2. 按照顺序执行下面指令3. 修改…

2024接口自动化测试高频面试题【建议收藏】

一、json和字典的区别&#xff1f; json就是一个文本、字符串&#xff1b;有固定的格式&#xff0c;格式长的像python字典和列表的组合&#xff1b;以key-value的键值对形式来保存数据&#xff0c;结构清晰&#xff0c;。可以说是目前互联网项目开发中最常用的一种数据交互格式…

buuctf——[CISCN2019 华北赛区 Day2 Web1]Hack World

buuctf——[CISCN2019 华北赛区 Day2 Web1]Hack World 1.根据提示&#xff0c;说明flag在表里 2.那就猜测存在sql注入&#xff0c;反手测试一波id1 3.尝试使用1--进行注释 4.直接丢进salmap里吧&#xff0c;不出意外多半是跑不出来 5.直接放入fuzz里进行测试 6.发现当我…

Springboot 结合PDF上传到OSS

目录 一、首先注册阿里云OSS&#xff08;新用户免费使用3个月&#xff09; 二、步骤 2.1 将pdf模板上传到oos 2.2 这里有pdf地址,将读写权限设置为共工读 ​编辑 三、代码 3.1 pom.xml 3.2 配置文件 3.3 oss model 3.4 配置类(不需要修改) 3.5 将配置类放入ioc容器 3.…

Linux之rocky8操作系统安装

一、rocky系统简介 CentOS宣布停止开发后&#xff0c;CentOS的原创始人Gregory Kurtzer在CentOS网站上发表评论宣布&#xff0c;他将再次启动一个项目以实现CentOS的最初目标。它的名字被选为对早期CentOS联合创始人Rocky McGaugh的致敬。rocky系统一个开源、社区拥有和管理、免…

VForm3的文件上传方式

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 http://122.227.135.243:9666/ 更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码&#xff1a…

快排非递归与计数排序

感谢大佬的光临各位&#xff0c;希望和大家一起进步&#xff0c;望得到你的三连&#xff0c;互三支持&#xff0c;一起进步 个人主页&#xff1a;LaNzikinh-CSDN博客 收入专栏:初阶数据结构_LaNzikinh篮子的博客-CSDN博客 文章目录 前言一.快速排序非递归二.数据结构栈与内存栈…

“电子商务”的红利还能存在多久?我想抖音回答了这个问题

哈喽~我是电商月月 互联网的发展有目共睹&#xff0c;网上交易&#xff0c;电子支付已经遍及每个人身边&#xff0c;两者结合&#xff0c;特别是电商行业&#xff0c;利用这个时期真的赚的盆满钵满 每年都有大批商家涌入电商这个赛道&#xff0c;但发展了那么多年&#xff0c…

B端界面:除了蓝色外,四条搞定清新明快的界面设计。

一、什么是清新明快风格 清新明快的设计风格是指在B端系统中使用明亮、清淡的色彩、简洁的布局和自然元素&#xff0c;以及轻快的动效&#xff0c;营造出轻松、愉悦的界面氛围。 二、哪些行业适用 这种设计风格适用于多个行业&#xff0c;特别是那些与创意、娱乐、健康、旅游…

Python turtle海龟绘制美国队长盾牌

使用Python中的turtle模块绘制美队盾牌 具体思路如下&#xff1a; 导入海龟库第1个圆&#xff1a;半径 200&#xff0c;红色填充第2个圆&#xff1a;半径 150&#xff0c;白色填充第3个圆&#xff1a;半径 100&#xff0c;红色填充第4个圆&#xff1a;半径 50&#xff0c;蓝色…

动态Web项目讲解+Demo

web流程演示 请求路径 请求路径明确要请求的是哪个servlet 请求方式 servlet含有两种请求方式&#xff1a;doGet和doPost doGet&doPost 返回数据就是httpResponse&#xff0c;返回给success 参数 包含在request当中 成功 上述流程任何一步都没出问题&#xff0c;就会…

Java学习笔记26(枚举和注解)

1.枚举和注解 1.1 枚举 ​ 1.枚举(enumeration) ​ 2.枚举是一组常量的集合 ​ 3.枚举属于一种特殊的类&#xff0c;里面只包含一组有限的特定的对象 1.枚举应用案例 ​ 1.不需要提供setXxx方法&#xff0c;因为枚举对象值通常为只读 ​ 2.对枚举对象/属性使用final st…