【数据结构】初探数据结构面纱:栈和队列全面剖析

news2024/9/27 23:26:11

【数据结构】初探数据结构面纱:栈和队列全面剖析

🔥个人主页大白的编程日记

🔥专栏数据结构


文章目录

  • 【数据结构】初探数据结构面纱:栈和队列全面剖析
    • 前言
    • 一.栈
      • 1.1栈的概念及结构
      • 1.2栈的结构选择
      • 1.3栈的实现
      • 1.4栈OJ
    • 二. 队列
      • 2.1队列的概念及结构
      • 2.2队列的应用
      • 2.3队列的选择
      • 2.4队列的实现
      • 2.5队列OJ
    • 后言

前言

哈喽,各位小伙伴大家好!今天咱们就正式开始学习数据结构了。我们今天要学习的数据结构分别是栈和队列。话不多说,咱们进入正题!向大厂冲锋!

一.栈

1.1栈的概念及结构

栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。

栈是一种遵循后进先出的结构。类似生活中我们生活中的弹夹,羽毛球桶等等。

在这里插入图片描述

  • 入栈
    入栈就是往栈顶增添数据

  • 出栈
    出栈就是在栈顶删除元素

1.2栈的结构选择

  • 数组

我们可以用数组实现栈用下标控制栈顶元素的入栈和出栈

  • 单链表
    单链表其实不好实现栈,因为出栈时会修改上一个节点的指针。但单链表无法找到上一个节点。

    所以我们把栈顶放在左边,栈顶是头节点,这样入栈出栈都可以。不需要修改上一个节点。
  • 双向链表
    为了解决单链表找上一个节点的问题,我们可以用双向链表来解决。

那这三个我们改选择那里一个呢?
首先我们可以先排除双向链表,因为它单链表还多了一个指针,多浪费了空间而且还要多维护一个指针。那单链表和数组我们选哪一个呢?其实都差不多。顺序表有扩容的问题。但是顺序表的缓存利用率高(文章有解释)。所以我们就选择数组吧。

1.3栈的实现

  • 栈的定义
    我们先定义一个栈结构体,里面放有栈数组的指针。top是栈顶元素的下标。capacity则是栈数组现在的空间大小。
typedef int STDataType;
typedef struct Stack
{
	STDataType* a;
	int top;
	int capacity;
}ST;
  • 栈的初始化
    我们先断言一下。然后把空间大小和top都初始化为0。
    top的初始化有两种方式
void STInit(ST* pst)
{
	assert(pst);
	pst->a = NULL;
	pst->capacity = pst->top = 0;//指向栈顶元素的下一个
}

一种是初始化为-1,代表top指向栈顶元素。为什么要给-1呢?
因为如果给0的话,当栈为空时,0既能表示栈为空也能代表栈有一个元素,下标为0。所以初始化要给-1。
第二种就是初始化给0,代表top指向栈顶元素的下一个位置。

  • 栈的销毁

栈的销毁我们先free销毁数组,然后再给数组指针给空。
top和capacity都给0表示栈为空。

void STDestroy(ST* pst)
{
	assert(pst);
	free(pst->a);
	pst->a = NULL;
	pst->capacity = pst->top = 0;
}
  • 入栈
    入栈我们需要先对栈判满,如果满了我们就扩容到原来的2倍。
    如果没开空间就先开4个空间。当我们没开空间时,a是空指针,此时realloc相当与malloc。然后再更新a和capacity。赋值x,top++。
void STPush(ST* pst, STDataType x)
{
	assert(pst);
	if (pst->capacity == pst->top)//栈满了
	{
		int newcapacity = pst->capacity == 0 ? 4 : 2 * pst->capacity;//未开空间就给4个空间,否则就在原来的空间扩容两倍
		STDataType* tmp = (STDataType*)realloc(pst->a,sizeof(STDataType)*newcapacity);
		if (tmp == NULL)
		{
			perror("realloc fail~");
			return;
		}
		pst->a = tmp;
		pst->capacity = newcapacity;
	}
	pst->a[pst->top++] = x;//赋值 top++
}
  • 出栈

出栈我们只需要控制top,让top–即可。

void STPop(ST* pst)
{
	assert(pst);
	assert(pst->top > 0);
	pst->top--;
}
  • 获取栈顶元素

因为我们top是指向栈顶元素的下一个,所以我们返回下标为top-1的元素

STDataType STTop(ST* pst)
{
	assert(pst);
	assert(pst->top > 0);
	return pst->a[pst->top-1];
}
  • 判空

top等于0时就是空。

bool STEmpty(ST* pst)
{
	assert(pst);
	return 0 == pst->top;
}
  • 栈的元素个数
    因为我们的top指向栈顶元素的下一个,就相当于栈的元素个数size。
    我们直接返回top即可。
int STSize(ST* pst)
{
	assert(pst);
	return pst->top;
}
  • 栈的遍历
    栈在遍历的时候先获取栈顶元素,然后在出栈。直到栈为空。
int main()
{
	ST s;
	STInit(&s);
	STPush(&s, 1);
	STPush(&s, 2);
	STPush(&s, 3);
	STPush(&s, 4);
	while (!STEmpty(&s))
	{
		printf("%d ", STTop(&s));
		STPop(&s);
	}
	STDestroy(&s);
	return 0;
}


注意栈有可能边入边出,这时的输出结果顺序就不是与与输入顺序相反了

int main()
{
	ST s;
	STInit(&s);
	STPush(&s, 1);
	STPush(&s, 2);
	printf("%d ", STTop(&s));
	STPop(&s);
	STPush(&s, 3);
	STPush(&s, 4);
	while (!STEmpty(&s))
	{
		printf("%d ", STTop(&s));
		STPop(&s);
	}
	STDestroy(&s);
	return 0;
}

1.4栈OJ

-题目
有效的括号

  • 思路分析
    让左括号入栈,右括号与左括号匹配。

  • 代码实现

typedef char STDataType;
typedef struct Stack
{
	STDataType* a;
	int top;
	int capacity;
}ST;
void STInit(ST* pst)
{
	assert(pst);
	pst->a = NULL;
	pst->capacity = pst->top = 0;
}
void STDestroy(ST* pst)
{
	assert(pst);
	free(pst->a);
	pst->a = NULL;
	pst->capacity = pst->top = 0;
}
void STPush(ST* pst, STDataType x)
{
	assert(pst);
	if (pst->capacity == pst->top)
	{
        int newcapacity = pst->capacity == 0 ? 4 : 2 * pst->capacity;
		STDataType* tmp = (STDataType*)realloc(pst->a,sizeof(STDataType)*newcapacity);
		if (tmp == NULL)
		{
			perror("realloc fail~");
			return;
		}
		pst->a = tmp;
		pst->capacity = newcapacity;
	}
	pst->a[pst->top++] = x;
}
void STPop(ST* pst)
{
	assert(pst);
	assert(pst->top > 0);
	pst->top--;
}
STDataType STTop(ST* pst)
{
	assert(pst);
	assert(pst->top > 0);
	return pst->a[pst->top-1];
}
bool STEmpty(ST* pst)
{
	assert(pst);
	return 0 == pst->top;
}
int STSize(ST* pst)
{
	assert(pst);
	return pst->top;
}
bool isValid(char* s) 
{
    ST t;
    STInit(&t);
    while(*s)
    {
        if(*s=='('||*s=='['||*s=='{')
        {
            STPush(&t,*s);//左括号入栈
        }
        else
        {
            if(STEmpty(&t))//没有左括号匹配
            {
                return false;
            }
           char tmp=STTop(&t);//获取栈顶元素匹配
           STPop(&t);
           if(*s==')'&&tmp!='('
           ||*s=='}'&&tmp!='{'
           ||*s==']'&&tmp!='[')//匹配
           {
             return false;
           }
        }
        s++;
    }
    bool ret=STEmpty(&t);//判空
    STDestroy(&t);
    return ret;

}

二. 队列

2.1队列的概念及结构

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


与栈相反,队列遵循先进先出的原则。只能在队尾入数据,队头出数据。

2.2队列的应用

  • 抽号机
    我们平时在日常生活中都会遇到取票排队。取票后我们就把票数尾插到抽号机里,要取票时我们就在队头出数据。这样就能保证先取票的先出号。
  • 好友推荐
    队列还可以做好友推荐。也就是广度优先遍历(DFS)。

2.3队列的选择

  • 顺序表
    顺序表不好实现队列。因为队列是队头出数据,顺序表头删需要挪动数据。

  • 双向链表
    双向链表其实实现啥都好。但是双向链表多开一个指针,浪费内存。还要多维护一个指针。

  • 单链表
    单链表实现队列非常合适。因为队列在队尾入数据,队头出数据。单链表头删和尾删都不需要上一个节点。

所以我们用单链表实现。

2.4队列的实现

  • 队列结构体
    我们先定义一个队列节点的结构体,然后在用一个头指针,一个尾指针,和一个size维护整个队列。
typedef struct QueueNode
{
	struct QueueNode* next;
	QDataType val;
}QNode;
typedef struct Queue
{
	QNode* phead;
	QNode* ptail;
	int size;
}Queue;
  • 队列的初始化
    我们把头指针和尾指针都初始化为空,size初始化为0.
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}
  • 队列的销毁

我们创建一个cur指针指向头节点,然后遍历销毁即可。注意要先保存下一个节点在销毁当前节点,然后移动cur即可。最后让头指针尾指针指向空。size为0即可。

void QueueDestroy(Queue* pq)
{
	assert(pq);
	QNode* cur = pq->phead;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	};
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}
  • 队列的插入
    我们malloc一个节点。因为是尾插,所以让节点指向空。赋值为x。如果没有节点,那头节点和尾节点都是指向新节点。否则尾插在尾节点后。新节点成为新的尾节点。最后size++。
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QNode* node = (QNode*)malloc(sizeof(QNode));
	if (node == NULL)
	{
		perror("malloc fail");
		return;
	}
	node->next = NULL;
	node->val = x;
	if (pq->phead == NULL)//没有节点
	{
		pq->phead = pq->ptail = node;
	}
	else//至少有一个节点
	{
		pq->ptail->next = node;
		pq->ptail = node;
	}
	pq->size++;
}

-获取队头元素

我们先断言一下判断队列是否为空,然后返回队头节点元素的值。

QDataType QueueFron(Queue* pq)
{
	assert(pq);
	assert(pq->size != 0);
	return pq->phead->val;
}
  • 获取队尾元素
    我们先断言一下判断队列是否为空,然后返回队尾节点元素的值。
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(pq->size != 0);
	return pq->ptail->val;
}
  • 队头的删除
    我们先断言一下判断队列是否为空,然后分两种情况
    第一种情况,当队列只有一个节点时。
    队头指针和队尾指针都指向空,size–。
    第二种情况,当队列不是一个节点时。
    保存队头节点的下一个节点,释放头节点,保存的节点成为新的头节点。size–。
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(pq->size != 0);
	if (pq->phead == pq->ptail)//只有一个节点
	{
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
		pq->size--;
	}
	else
	{
		QNode* next = pq->phead->next;
		free(pq->phead);
		pq->phead = next;
		pq->size--;
	}
}

  • 队列的判空

判断size是否为0即可

bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->size == 0;
}
  • 队列的元素个数

因为我们前面用size记录了个数,直接返回size即可。
防止遍历找.实现O(1).

int QueueSize(Queue* pq)
{
	assert(pq);
	return pq->size;
}
  • 队列的遍历

队列的遍历就是获取队头元素,然后删除队头元素直到队列为空。

int main()
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, 1);
	QueuePush(&q, 2);
	QueuePush(&q, 3);
	QueuePush(&q, 4);
	while (q.size)
	{
		printf("%d ", QueueFron(&q));
		QueuePop(&q);
	}
	QueueDestroy(&q);
	return 0;
}

注意不论是否边入边出。队列输出的顺序都与入队列顺序一致。

int main()
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, 1);
	QueuePush(&q, 2);
	printf("%d ", QueueFron(&q));
	QueuePop(&q);
	printf("%d ", QueueFron(&q));
	QueuePop(&q);
	QueuePush(&q, 3);
	QueuePush(&q, 4);
	while (q.size)
	{
		printf("%d ", QueueFron(&q));
		QueuePop(&q);
	}
	QueueDestroy(&q);
	return 0;
}

2.5队列OJ

  • 题目
    用队列实现栈

  • 思路分析
    我们保持一个队列有数据,一个队列没数据。
    出栈时,往空队列导入数据即可拿到栈顶元素。

  • 代码实现

typedef int QDataType;
typedef struct QueueNode
{
	struct QueueNode* next;
	QDataType val;
}QNode;
typedef struct Queue
{
	QNode* phead;
	QNode* ptail;
	int size;
}Queue;
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}
void QueueDestroy(Queue* pq)
{
	assert(pq);
	QNode* cur = pq->phead;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	};
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QNode* node = (QNode*)malloc(sizeof(QNode));
	if (node == NULL)
	{
		perror("malloc fail");
		return;
	}
	node->next = NULL;
	node->val = x;
	if (pq->phead == NULL)
	{
		pq->phead = pq->ptail = node;
	}
	else
	{
		pq->ptail->next = node;
		pq->ptail = node;
	}
	pq->size++;
}
QDataType QueueFron(Queue* pq)
{
	assert(pq);
	assert(pq->size != 0);
	return pq->phead->val;
}
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(pq->size != 0);
	return pq->ptail->val;
}
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->size == 0;
}
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(pq->size != 0);
	if (pq->phead == pq->ptail)
	{
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
		pq->size--;
	}
	else
	{
		QNode* next = pq->phead->next;
		free(pq->phead);
		pq->phead = next;
		pq->size--;
	}
}
//统计个数
int QueueSize(Queue* pq)
{
	assert(pq);
	return pq->size;
}
typedef struct {
    Queue q1;
    Queue q2;
} MyStack;
MyStack* myStackCreate() {
    MyStack* q=(MyStack*)malloc(sizeof(MyStack));
    QueueInit(&(q ->q1));
    QueueInit(&(q->q2));
    return q;
}
void myStackPush(MyStack* obj, int x) {
    if(!QueueEmpty(&obj->q1))//往不为空的队列入数据
    {
        QueuePush(&obj->q1,x);
    }
    else
    {
        QueuePush(&obj->q2,x);
    }
}
int myStackPop(MyStack* obj) {
    Queue* empty=&obj->q1;
    Queue* noempty=&obj->q2;
    if(!QueueEmpty(&obj->q1))
    {
        noempty=&obj->q1;
        empty=&obj->q2;
    }//假设法
    while(QueueSize(noempty)>1)//数据入队列直到剩下一个数据
    {
        QueuePush(empty,QueueFron(noempty));
        QueuePop(noempty);
    }
    int top=QueueFron(noempty);
    QueuePop(noempty);
    return top;
}
int myStackTop(MyStack* obj) {
    if(!QueueEmpty(&obj->q1))//返回不为空的队尾元素
    {
        return QueueBack(&obj->q1);
    }
    else
    {
        return QueueBack(&obj->q2);
    }
}
bool myStackEmpty(MyStack* obj) {
    return QueueEmpty(&obj->q1) && QueueEmpty(&obj->q2);//判断两个队列是否为空
}
void myStackFree(MyStack* obj) {
    QueueDestroy(&obj->q1);//销毁队列1
    QueueDestroy(&obj->q2);//销毁队列2
    free(obj);//销毁结构体
    obj=NULL;
}

后言

这就是栈和队列的内容。栈和队列都是很重要的数据结构。大家一定要多加掌握和熟练。今天就分享到这里。感谢大家的耐心垂阅,咱们下期见!拜拜~
在这里插入图片描述

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

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

相关文章

Xilinx FPGA:vivado fpga与EEPROM的IIC通信,串口显示数据,含使用debug教程

一、实验要求 实现FPGA与EEPROM的通信,要求FPGA对EEPROM实现先“写”后“读”,读出的值给uart发送端并显示到电脑上,按下按键1让fpga对EEPROM写入数据;按下按键2让fpga读出对EEPROM写入过的数据。 二、信号流向图 三、程序设计 …

Spring-Spring、IoC、DI、注解开发

1、Spring是什么 Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器(框架)。 Spring整体架构 Spring优点: Spring属于低侵入设计。IOC将对象之间的依赖关系交给Spring,降低组件之间的耦合,实现各个层之间的解耦,让我们更专注于业务…

Day1每日编程题日记:数字统计、两个数组的交集、点击消除

前言:该篇用于记录自看。曾回看昨天的做题代码,竟然会觉得陌生,这竟然是我写的,细细读了一下,原来我当时是这么想的。因此我觉得记代码没有实际用处,重点是领悟了思想,这样子代码就在心中&#…

怎样将aac转换mp3格式?推荐四个aac转MP3的方法

怎样将aac转换mp3格式?当需要将aac格式音频转换为MP3格式时,有几种方法可以轻松实现这一目标。MP3是一种广泛支持的音频格式,几乎所有设备和平台都能播放MP3文件,包括各种音乐播放器、手机、平板电脑和汽车音响系统。而且它也提供…

MQTT协议网关解决方案及实施简述-天拓四方

MQTT协议网关是一个中间件,负责接收来自不同MQTT客户端的消息,并将这些消息转发到MQTT服务器;同时,也能接收来自MQTT服务器的消息,并将其转发给相应的MQTT客户端。MQTT协议网关的主要功能包括协议转换、消息过滤、安全…

YOLOv10改进 | 主干/Backbone篇 | 利用RT-DETR特征提取网络PPHGNetV2改进YOLOv10

一、本文介绍 本文给大家带来利用RT-DETR模型主干HGNet去替换YOLOv10的主干,RT-DETR是今年由百度推出的第一款实时的ViT模型,其在实时检测的领域上号称是打败了YOLO系列,其利用两个主干一个是HGNet一个是ResNet,其中HGNet就是我们…

会声会影分割音频怎么不能用 会声会影分割音频方法 会声会影视频制作教程 会声会影下载免费中文版2023

将素材中的音频分割出来,对声音部分进行单独编辑,是剪辑过程中的常用操作。会声会影视频剪辑软件在分割音频后,还可以对声音素材进行混音编辑、音频调节、添加音频滤镜等操作。有关会声会影分割音频怎么不能用,会声会影分割音频方…

加密软件|让数据传输更安全

加密软件在当今数字化时代扮演着至关重要的角色,它们通过先进的加密算法和技术,确保数据在存储、传输和分享过程中的安全性,从而保护个人隐私和企业机密。一、加密软件的基本作用数据加密:加密软件通过应用复杂的加密算法&#xf…

回归树模型

目录 一、回归树模型vs决策树模型:二、回归树模型的叶结点:三、如何决定每个非叶结点上的特征类型: 本文只介绍回归树模型与决策树模型的区别。如需了解完整的理论,请看链接:决策树模型笔记 一、回归树模型vs决策树模…

欣奇随机美图源码

欣赏养眼美图让人心情愉悦 新增正能量进站引导首页 上传文件解压即可用有手就行 美图输出接口自判断版 http://mt.xqia.net/api.php http://mt.xqia.net/api.php?typejson 源码下载:https://download.csdn.net/download/m0_66047725/89520368 更多资源下载&…

Kotlin MultiPlatform(KMP)

Kotlin MultiPlatform 1.KMP 是什么 Kotlin Multiplatform 是一个工具,它让我们用同一种编程语言(Kotlin)写代码,这些代码可以同时在不同的设备上运行,比如手机、电脑和网页。这样做可以节省时间,因为你不…

昇思25天学习打卡营第23天|K近邻算法实现红酒聚类

学AI还能赢奖品?每天30分钟,25天打通AI任督二脉 (qq.com) K近邻算法实现红酒聚类 本实验主要介绍使用MindSpore在部分wine数据集上进行KNN实验。 1、实验目的 了解KNN的基本概念;了解如何使用MindSpore进行KNN实验。 2、K近邻算法原理介绍…

液晶电子看板助力工厂打造车间精益管理数字化

在当今竞争激烈的市场环境下,企业追求生产智能化已成为提升竞争力的关键。要实现这一目标,生产现场设备联网进行数据采集是首要且基础的步骤。工业生产设备作为核心要素,其产生的各类数据与产品质量及企业运营效率息息相关。 在这样的背景下&…

自然语言处理基本概念

自然语言处理基本概念 所有学习循环神经网络的人都是看这一篇博客长大的: https://colah.github.io/posts/2015-08-Understanding-LSTMs/ import jieba import torch from torch import nns1 "我吃饭了!" s2 "今天天气很好&#xff01…

加减计数器

目录 描述 输入描述: 输出描述: 参考代码 描述 请编写一个十进制计数器模块,当mode信号为1,计数器输出信号递增,当mode信号为0,计数器输出信号递减。每次到达0,给出指示信号zero。 模块的接…

【鸿蒙学习笔记】使用httpRequest进行HTTP数据请求

官方文档:网络管理开发概述 目录标题 访问淘宝公开接口(测试数据)第1步:module.json5 配置网络授权第2步:源码第3步:启动模拟器第4步:启动entry第5步:操作 访问淘宝公开接口&#x…

区分modbus tcp和tcp/ip

Modbus 对某些人来说,这听起来可能很复杂,也很令人费解,但是一旦你了解了它的工作原理,那就是一个特别简单的过程。MODBUS 这是一种请求和响应协议。MODBUS 主站将发起请求,从站将响应错误或请求信息。这就是 modbus 简…

海外媒体软文发稿:南非新闻通稿宣发,谷歌新闻收录

南非媒体介绍 南非作为非洲大陆最先进的经济体之一,拥有着丰富多样的媒体资源。在南非,各种类型的新闻报纸、杂志和网站充斥着市场。以下是一些南非主要媒体的 大舍传媒 大舍传媒是专注南非最有影响力的发稿平台之一,其新闻报道覆盖了南非…

【深度学习】基于深度学习的模式识别基础

一 模式识别基础 “模式”指的是数据中具有某些相似特征或属性的事物或事件的集合。具体来说,模式可以是以下几种形式: 视觉模式 在图像或视频中,模式可以是某种形状、颜色组合或纹理。例如,人脸、文字字符、手写数字等都可以视…

【鸿蒙学习笔记】通过用户首选项实现数据持久化

官方文档:通过用户首选项实现数据持久化 目录标题 使用场景第1步:源码第2步:启动模拟器第3步:启动entry第6步:操作样例2 使用场景 Preferences会将该数据缓存在内存中,当用户读取的时候,能够快…