【(C语言)数据结构奋斗100天】栈和队列

news2024/12/22 10:05:30

前言

🏠个人主页:泡泡牛奶

🌵系列专栏:[C语言] 数据结构奋斗100天

本期所介绍的是栈和队列,那么什么是栈呢,什么是队列呢?在知道答案之前,请大家思考一下下列问题:

  1. 你如何理解物理结构和逻辑结构?
  2. 栈的规则是什么?
  3. 栈和队列最重要的基本操作有哪些?
  4. 什么是循环队列?

让我们带着这些问题,开始今天的学习吧( •̀ ω •́ )✧

文章目录

  • 前言
  • 一、物理结构?逻辑结构?
  • 二、栈 (stack)
    • 1. 什么是栈?
    • 2. 栈类型的定义
      • 1) 用链表来实现
      • 2) 用数组来实现
    • 3. 栈的基本操作
      • 1) 栈的初始化
      • 2) 栈的销毁
      • 3) 判断栈是否为空
      • 4) 栈的元素个数
      • 5) 取栈顶元素
      • 6) 压栈✨
      • 7) 出栈✨
  • 三、队列 (queue)
    • 1. 什么是队列?
    • 2. 队列类型的定义
      • 1) 用链表来实现
      • 2) 用数组来实现
    • 3. 队列的基本操作
      • 1) 队列的初始化
      • 2) 队列的销毁
      • 3) 判断队列是否为空
      • 4) 队列的元素个数
      • 5) 取队首元素
      • 6) 取队尾元素
      • 7) 入队✨
      • 8) 出队✨
    • 4. 循环队列
  • 四、小结

一、物理结构?逻辑结构?

物理结构是指的是数据在计算机内存中的存储方式。数组是在内存中连续存放,链表则是通过指针将各个节点的地址串连起来。前者随机访问速度快,后者插入删除效率高。

**逻辑结构指的是数据 在一定逻辑规则下限制 的组织方式。**逻辑结构往往需要通过物理结构加以规则限制来实现想要的功能。所以你也可以说逻辑结构是为了满足人的需求的一种结构。就例如我接下说的栈和队列。

总的来说,数据结构中的物理结构关注的是数据在计算机内存中的存储方式,而逻辑结构关注的是数据在逻辑上的组织方式。在使用数据结构时,通常需要考虑这两个方面,以便确定最合适的数据结构。

image-20221218234522635

二、栈 (stack)

1. 什么是栈?

要想了解什么是栈,我们可以用一个简单的例子。假设我们有一个羽毛球筒,一端封口,另一端开口。现在要往羽毛球筒里装羽毛球,先放入的靠近底部,后放入的靠近入口。

image-20221216175427689

现在我们将球装入球筒后,想将最里面的球拿出来,那么只能先将最靠近入口的球先拿出来。先放进去的后取出来,后放进去的先取出来。

image-20221216175922675

栈(stack)就像这个羽毛球筒,是一种特殊的线性表,满足一种先进后出(First In Last Out,简称FILO)限制规则。最早进入的元素存放在栈底(bottom),最后进入的元素在栈顶(top)。

知道了限制规则,我们可以用数组或者链表这些物理结构来实现

栈若要用数组实现。只要压入的元素等于数组的容量,就要考虑扩容。

image-20221216234443748

栈若要用链表来实现。压栈的操作可以想象成头插,出栈的操作可以想象成头删,只要注意 没有元素时的处理就好了。

image-20221216234618043

2. 栈类型的定义

1) 用链表来实现

typedef int StackType;//定义元素类型,方便后续修改

typedef struct StackValue
{
    StackType value;
    struct StackValue* next;//指向下一个节点的地址
}StackValue;

typedef struct Stack
{
    size_t size;//方便确定栈的大小
    StackValue* top;//方便找到头节点
}Stack;

在接下来的内容中,不会有具体的代码展现,但是会提供用链表实现的思路(还是希望大家在学数据结构时能开阔自己的思维φ(゜▽゜*)♪

这样的写法只是给大家一个参考,如果在你看完本篇博客后有兴趣想用链表来实现栈,那么可以尝试一下( •̀ ω •́ )✧,不要只局限于一种物理结构上。

2) 用数组来实现

选用数组的原因:

  1. 尾插尾删效率很高,不用重复像内存申请空间再释放空间
  2. CPU高速缓存命中率更高。CPU在扫描时会按照一定大小(三级缓存)扫描缓存,因为数组是一块连续的空间,扫描时更容易一次性扫描完
typedef int StackType;//定义元素类型,方便后续修改

typedef struct Stack
{
    StackType* arr;
    size_t top;
    size_t capacity;
}Stack;

3. 栈的基本操作

1) 栈的初始化

从写代码接口的角度来看,任何简单的工作最好通过一个接口式函数来实现,因为使用者并不知道你的底层是如何实现的(万一使用的是链表来实现呢(o゚v゚)ノ

  • 以数组为例,代码实现如下:
void stack_init(Stack* pst)
{
    assert(pst);
    
    pst->arr = NULL;
    pst->top = pst->capacity = 0;
}

2) 栈的销毁

为了防止内存的泄露,结束使用需要对栈进行销毁。

void stack_distory(Stack* pst)
{
    assert(pst);
    
    free(pst->arr);
    pst->arr = NULL;
    pst->capacity = pst->top = 0;
}

3) 判断栈是否为空

数组实现判断是否为空,只要看top是否为0。链表实现需要看top指向的地址是否为NULL

bool stack_empty(const Stack* pst)
{
    assert(pst);
    return pst->top == 0;
}

4) 栈的元素个数

我们设计的栈top表示的数组的下标,所以取元素个数的时候,需要+1

有些封装top直接表示元素个数,所以这就是封装的好处,随意暴露底层,底层的结构容易被破坏,使用者并不需要知道底层是什么(这里提一下,有些栈实现下标是从-1开始的,也许你会在某些地方看到,不要觉得奇怪(*^-^*)

size_t stack_size(const Stack* pst)
{
    assert(pst);
    return pst->top+1;
}

5) 取栈顶元素

因为我们设计的栈 top 表示的最后一个元素的数组下标,所以可以直接返回 arr[top]

StackType stack_top(const Stack* pst)
{
    assert(pst);
    
    return pst->arr[pst->top];
}

6) 压栈✨

栈的插入操作被叫做压栈/进栈/入栈( push ),压入的数据在栈顶

  • 以数组为例子:注意如果数组满了,要考虑开新空间

栈_1代码实现如下:

void stack_push(Stack* pst, StackType data)
{
    //断言指针有效性
    assert(pst);
    
    if (pst->top == pst->capacity)
    {
        //扩容
        size_t newcapacity = pst->capacity==0 ? 4 : 2*pst->capacity;
        StackType* tmp = (StackType*)realloc(pst->arr, newcapacity*sizeof(StackType));
        //检查扩容后指针的有效性
        if (tmp == NULL)
        {
            perror("push realloc fail");
            exit(-1);
        }
        pst->arr = tmp;
        pst->capacity = newcapacity;
    }
    
    pst->arr[pst->top] = data;
    ++pst->top;
}
  • 以链表为例:链表头插之后更新top指向的头节点就好了

栈_1_2

7) 出栈✨

栈元素的删除被叫做出栈( pop )。出栈就是要将栈顶的元素弹出,只有栈顶元素才允许出栈。

  • 以数组为例:数组不考虑缩容的操作,缩容代表你要重新申请空间,并将原来的数据拷贝回去,大大降低了效率。
栈_2_1

代码实现如下:

void stack_pop(Stack* pst)
{
    assert(pst);
    //判断栈不为空,这个函数下面会实现 pst->top == 0
    assert(!stack_empty(pst));
    
    --pst->top;
}
  • 以链表为例:将头节点删掉之后,更新top的节点(要注意当top指向为NULL时,说明已经栈为空的,不能继续删除。

栈_2_2

三、队列 (queue)

1. 什么是队列?

要知道什么时队列,我们同样可以用一个生活中的例子。假设有一排人排队打饭,食堂阿姨需要给这一排人打饭,不允许插队,那么新来的同学想打饭,只能排在队伍的后面。

image-20221217223910178

食堂阿姨给最前面的人打完饭之后,最前面的人就可以离开队伍。

image-20221217224404451

而队列就是这么一个道理,是一种特殊的线性表,队列的元素只能先进先出 ( First In First Out , 简称 FIFO )。队列的出口叫 队头( front ) ,队列的出口叫 队尾 (rear)

与栈类似,队列既可以用数组实现,也可以用链表来实现。但用数组实现时,为了入队操作方便,需要将队尾的位置规定为最后入队元素的下一个位置

image-20221217225900445

队列的链表实现如下。

image-20221217230633889

2. 队列类型的定义

1) 用链表来实现

选用链表的原因:

  1. 插入数据操作方便
  2. 空间按需申请,节省空间,避免不必要的空间浪费
typedef int QueueType;

typedef struct QueueNode
{
    QueueType value;
    struct QueueNode* next;
}QueueNode;

typedef struct Queue
{
    QueueNode* front;
    QueueNode* rear;
    size_t size;
}Queue;

2) 用数组来实现

想要用数组来实现,当我们有多个元素时,要进行出队操作,可以移动front达到头删的操作,但假设元素有很多,那势必会造成不必要的空间浪费,在实际开发中不推荐使用(但在用C语言刷题的时候经常会用到

栈_3_2

3. 队列的基本操作

1) 队列的初始化

void queue_init(Queue* pque)
{
    assert(pque);
    pque->front = pque->rear = NULL;
    pque->size = 0;
}

2) 队列的销毁

用链表来实现队列,当销毁链表的时候,需要将链表的每一个节点都删除(防止内存泄漏

void queue_destory(Queue* pque)
{
    assert(pque);
    QueueNode* cur = pque->front;
    
    while (cur != NULL)
    {
        QueueNode* del = cur;
        cur = cur->next;
        
        free(del);
    }
    pque->front = pque->rear = NULL;

3) 判断队列是否为空

代码如下:

void queue_empty(const Queue* pque)
{
    assert(pque);
    
    return ((pque->front == NULL) && (pque->rear == NULL));
}

4) 队列的元素个数

代码如下:

void queue_size(const Queue* pque)
{
    assert(pque);
    
    return pque->size;
}

5) 取队首元素

代码如下:

QueueType queue_front(const Queue* pque)
{
    assert(pque);
    
    //判断队列不为空
    assert(!queue_empty(pque));
    
    return pque->front->value;
}

6) 取队尾元素

代码如下:

QueueType queue_back(const Queue* pque)
{
    assert(pque);
    
    //判断队列不为空
    assert(!queue_empty(pque));
    
    return pque->rear->value;
}

7) 入队✨

入队就是将新的元素放入队列中,只允许在队尾的位置放入元素,新元素会成为新的队尾。

队列_3

要注意,当队列为空的时候(frontrear 都为 NULL),rear无法指向下一个节点,所以要进行特殊处理。

队列_4

代码实现如下:

void queue_push(Queue* pque, QueueType value)
{
    assert(pque);
    
    //创建新节点
    QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
    if (newnode == NULL)
    {
        perror("push malloc error");
        exit(-1);
    }
    
    //给新节点赋值
    newnode->value = value;
    newnode->next = NULL;
    
    //特殊处理,若队列为空
    if (queue_empty(pque))
    {
        //让队头和队尾 等于 新节点
        pque->front = pque->rear = newnode;
    }
    else
    {
        pque->rear->next = newnode;
        pque->rear = newnode;
    }
    
    ++pque->size;
    
}

8) 出队✨

出队操作就是把元素移出队列,只允许在队头一侧移出元素,出队的后一个元素会成为新的队头。

队列_1

要注意,当队列只有一个元素的时候,队头和队尾是一个位置,需要把两个都置为NULL才行。

队列_2

代码实现如下:

void queue_pop(Queue* pque)
{
    assert(pque);
    assert(!queue_empty(pque));
    
    //特殊处理,只有一个元素的时候,删除节点并将头尾置空
    if (pque->front->next ==  NULL)
    {
        free(pque->front);
        pque->front = pque->rear = NULL;
    }
    else
    {
        QueueNode* del = pque->front;
        pque->front = pque->front->next;
        
        free(del);
        del = NULL;
    }
    
    --pque->size;
    
}

4. 循环队列

循环队列指的是头尾相接的一种队列。

  • 若考虑用数组实现想要实现头尾相接的效果,可以考虑取模操作% ,那么这样一来,数组的大小一定是固定的大小,假设循环队列长度为N,那么有效长度就为 (front - rear + N) % N其中frontrear都表示数组的下标。

    image-20221219005646485
  • 若考虑用链表来实现,可以考虑使用单向循环链表,但相对的效率会有所降低,得到的是无固定大小,有效长度就是链表节点的个数。

    image-20221219010842701

四、小结

  • 什么是栈

栈是一种线性逻辑结构,可以用数组实现,也可以用链表实现。栈包括压栈和出栈的操作,遵循先进后出的原则(FILO)

  • 什么是队列

队列是一种线性逻辑结构,可以用数组实现,也可以用链表实现。队列包括入队和出队操作,遵循先进先出的原则(FIFO)。队列一种特殊的形式,叫循环队列,循环队列在队列的基础上遵循着头尾相接的原则。

如果这篇博客对你有帮助的话,还不忘给一个大大的三连支持博主,你的关注的是我最大的动力😚😚😚,我们下期再见。

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

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

相关文章

【问答篇】Java 线程篇 面试题(二)

每天进步一点~ (ps: 文章内容及图片出处来自本人公众号~) 01、问:请谈谈你对线程声明周期的6种状态的认识和理解 答:很多地方说线程有5种状态,但实际上是6种状态:NEW、RUNNABLE, BLOCKED、 WAITING、TIMED_WAITING、TERMINATED; 新创建&a…

(附源码)Springboot掌上博客系统 毕业设计 063131

Springboot掌上博客系统的设计与实现 摘 要 掌上博客系统是当今网络的热点,博客技术的出现使得每个人可以零成本、零维护地创建自己的网络媒体,Blog站点所形成的网状结构促成了不同于以往社区的Blog文化,Blog技术缔造了“博客”文化。 本文课…

微服务框架 SpringCloud微服务架构 服务异步通讯 53 MQ 集群 53.1 集群分类 53.2 普通集群

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式,系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 服务异步通讯 文章目录微服务框架服务异步通讯53 MQ 集群53.1 集群分类53.1.1 集群分类53.2 普通集群53.2.1 普通集群53.2.2 搭建普通 集群5…

2022 FIFA World Cup Final

我希望梅西能够捧杯,因为我怕再看见那个眼神!写在总决赛开始前 12/18/2022 22:04 在一个周日的晚上收到了邹总发的活动信,我记得还在CSDN问答区在回答问题,突然看见私信的红点,其实看到活动(活动链接点击这…

万字长文,彻底搞懂分布式缓存Redis

最近系统性地整理了Redis的知识点,在此和大家做些分享,希望能帮助到大家。 为什么Redis这么受欢迎 时代产物 随着互联网规模的不断扩张,越来越多的企业在技术架构上会采用分布式架构,而且对于系统的吞吐量以及响应速率的要求也…

非零基础自学Golang 第11章 文件操作 11.2 文件基本操作 11.2.3 文件写入 11.2.4 删除文件

非零基础自学Golang 文章目录非零基础自学Golang第11章 文件操作11.2 文件基本操作11.2.3 文件写入11.2.4 删除文件第11章 文件操作 11.2 文件基本操作 11.2.3 文件写入 与之前的文件读取相比,向文件写入内容也有两个接口,分别为Write和WriteAt。 fu…

数据管理篇之元数据

第12章 元数据 1.元数据概述 元数据定义 元数据是关于数据的数据。按照用途可以分为两类: 技术元数据 业务元数据 阿里巴巴常见的技术元数据: 分布式计算系统存储元数据 分布式计算系统运行元数据 数据开发平台中数据同步,计算任务、任务调…

【编译原理】第四章部分课后题答案

第 四 章 课 后 习 题 T 4.1 根据表4.1的语法制导定义,为输入表达式5∗(4∗32)5*(4*32)5∗(4∗32)构造注释分析树。 T 4.2 构造表达式((a∗b)(c))((a*b)(c))((a∗b)(c))的分析树和语法树: (a)根据表4.3的语法制导定义。 &…

C++中你不知道的namespace和using的用法

目录 引言 一: 冒号作用域 二、名字控制 1 命令空间 2 命令空间的使用 三、 using的指令 1 using的声明 2 using的编译指令 引言 你是不是只认为namespace 和 using 在C中是基本的语法框架,但是却不知道它们的真正用法,看完文章你会对using和name…

计算机毕设Python+Vue校园志愿者服务系统(程序+LW+部署)

项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: SSM mybatis Maven Vue 等等组成,B/S模式 M…

软件测试零基础如何快速入门 ?这里有全网最详细的学习资料

目录 前言 一、首先,我们要了解清楚用人部门对初级测试人员的定位: 二、清楚了初级测试人员需要具备的能力 三、找到正确的方向 四、最后需要做的就是储备自己的能力。 一.找本软件测试基础的书 二.写文档 三.执行测试 四.多关注技术博文 五、…

城市管理网站

开发工具(eclipse/idea/vscode等): 数据库(sqlite/mysql/sqlserver等): 功能模块(请用文字描述,至少200字): “模块划分:公告类型,公告信息,城管信息,居民信息,设诉类型&…

工程师为微型晶体管开发新的集成路线

新南威尔士大学悉尼团队展示了高 κ 钙钛矿膜如何充当二维晶体管的绝缘体 新南威尔士大学悉尼分校的研究人员开发了一种微小、透明且灵活的材料,可用作晶体管中的新型电介质(绝缘体)组件。 最近发表在《自然》杂志上的研究“高 κ 钙钛矿膜…

面试题61. 扑克牌中的顺子

晚上做了道题,写完看了大佬的题解发现自己很蠢,思维不够光想着模拟了,来回考虑细节磕磕绊绊写完这么一道题。虽然也是写出来了,复杂度都是ok的,不过代码长,处理细节麻烦。 记录一下这道题 从若干副扑克牌中…

对DataFrame的列标签增加后缀的DataFrame.add_suffix()方法

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 为DataFrame的列标签增加后缀 DataFrame.add_suffix() [太阳]选择题 关于以下python代码说法错误的一项是? import pandas as pd df pd.DataFrame({"A": [1,2],"B":[1…

基于Unity整合BEPUphysicsint物理引擎实战

上一节我们详细的讲解BEPUphysicsint 的物理事件。此物理引擎会产生了碰撞事件与非碰撞事件,碰撞事件大家好理解,非碰撞事件例如: 物理Entity的update事件,Entity的activation/deactivation事件等。本节课来实战如何编译BEPUphysicsint源码到自己的项目,…

Linux 服务器数据同步利器

一、简介 1 认识 Rsync(remote synchronize)是一个远程数据同步工具,可通过LAN/WAN快速同步多台主机间的文件。Rsync使用所谓的“Rsync算法”来使本地和远 程两个主机之间的文件达到同步,这个算法只传送两个文件的不同部分&#x…

【毕业设计_课程设计】基于机器视觉的害虫种类及数量检测(源码+论文)

文章目录0 项目说明1 研究目的2 研究内容及结论3 文件介绍4 论文目录5 项目源码0 项目说明 基于机器视觉的害虫种类及数量检测 提示:适合用于课程设计或毕业设计,工作量达标,源码开放 1 研究目的 研究的目的在于建立一套远程病虫害自动识别…

UNION 和 UNION ALL

合并查询结果 利用UNION关键字,可以给出多条SELECT语句,并将它们的结果组合成单个结果集。合并时,两个表对应的列数和数据类型必须相同,并且相互对应。 各个SELECT语句之间使用UNION或UNION ALL关键字分隔。 语法格式 SELECT c…

(附源码)node.js外卖平台 毕业设计 151448

摘 要 随着科学技术的飞速发展,各行各业都在努力与现代先进技术接轨,通过科技手段提高自身的优势;对于外卖平台当然也不能排除在外,随着网络技术的不断成熟,带动了外卖平台,它彻底改变了过去传统的管理方式…