欢迎光临散文网 会员登陆 & 注册

【D1n910】【郝斌】-数据结构入门学习笔记30-34 栈相关

2022-03-31 20:41 作者:爱交作业的D1N910  | 我要投稿

正常操作,正常分析,大家好我是D1N910。

下面是我学郝斌老师的数据结构入门的学习笔记。

那么开卷。

链表的我已经学完了,后面可能会补。


线性结构的两种常见应用 栈/队列,本文讲的是栈。

本文学习自:


栈的定义/分类/算法/应用

0、前言

首先要说一下计算机的内存。什么是计算机的内存呢?

内存(Memory)是计算机的重要部件,也称内存储器和主存储器,它用于暂时存放CPU中的运算数据,以及与硬盘等外部存储器交换的数据。

内存分为静态内存(栈)/动态内存(堆)

静态内存是在栈里分配的,操作系统自动分配的。

动态内存是在堆里分配的,堆是要程序员主动建立起来的。

举个实际的例子,比如说下面的 1.c 文件有变量 m、p、i、p,同样也有 200、100 这两个变量,前者存在栈里,后者存在堆里。栈是计算机进行分配分配的。堆是程序员进行分配的。

为什么说是程序员分配的堆呢?因为后面200等可能会做修改,所以是程序员进行分配的。

栈、堆表示分配数据的一种方式,静态/局部变量一样以出栈的方式分配内存;堆是以堆排序的方式分配内存。

1、栈定义

栈就是一种可以实现“先进后出”的存储结构(存储方式)。只要我们搞出一个东西能够先进后出存储数据,那么就是栈 —— 这像是鸭式辨型的概念,鸭式辨型的通俗说法是:“如果它走起路来像鸭子,叫起来也是鸭子,那么它就是鸭子。

栈类似于箱子装书,先进后出。先放进去的最后取出来。


x 不是这种横着装书啊!

2、栈分类(静态栈、动态栈)

2.1、静态栈

以数组为内核,想要删掉其中一个,要把想要删最后的全部拿出来 ——

因为是以数组为内核,所以栈具有数组的特性,长度是固定的、每个数据之间的地址是连续的,会先占一定的内存,所以是静态栈。

2.2、动态栈

栈的特性和链表相似,每个栈元素之间的地址不相连。和链表比砍了一些功能,只能从头部加和删,不能够在尾部进行加和删。也叫链式栈。

4->3->2->1->0

2.3、队列定义

先进先出。类似排队买东西,只有两个口,一个入口,一个出口,只能够从一端添加元素,只能够从另一端减少元素。

3、栈的算法

一般就是 出栈/压栈 两个算法

类似生产者-消费者模式,先把生产好的东西直接压进去,消费的时候直接从里面拿东西进行消费。

4、栈程序演示(33_栈程序演示 55:24)

一般的动态栈程序如上演示,栈的每个元素都是有意义的,PTop永远指向栈的顶部,PButtom永远指向栈的底部。所以PTop会疯狂动和修改。但是这里有一个问题就是栈的每一个元素都是有意义的,特别是PButtom指向的也是有意义的元素,不太方便处理。

那么就参考链表中头结点的设计。PButtom指向没有意义的头节点。

【入栈】如果要入栈的话,就先生产一个节点,然后往栈中接入,把PTop往上移一位;

【判空】判断栈是否为空,只要PTop和PButton相等就行

【判满】动态栈是链表,不存在满不满的问题~

下面就演示这个程序。

4.1、栈程序演示-头文件的引入

#include <stdio.h>

#include <stdlib.h>

4.2、栈程序演示-定义栈和栈对应链表结构体

typedef struct Node // 箱子里的书

{

   int data;

   struct Node * pNext;

} NODE, * PNODE;

typedef struct Stack // 栈  - 箱子

{

   PNODE pTop; // 指向栈的顶部

   PNODE pBottom; // 指向栈的底部

} STACK, * PSTACK;

4.3、栈程序演示-定义栈的基本算法(初始化/压栈/遍历/判断栈是否为空/出栈/清空栈内容)

int main(void)

{

   STACK S; // STACK 等价于 struct Stack

上面可以在main中声明栈变量 S,现在 S 有PTop 和 PButtom,PTop 和 PButtom还没指向任何东西。我们要初始化栈,初始化方法名可自定义,一般叫init,对应的还有其他基本算法,push: 压栈,往栈中添加元素;traverse:遍历输出栈的元素

init 初始化栈

给栈的pTop分配一个节点内存地址;

如果分配的地址为NULL,说明分配失败:

-打印报错信息,并退出程序。

如果分配的地址不为NULL,说明分配成功:

-栈的pBottom也指向栈的pTop指向的节点;

-栈的pTop指向的节点存储的下一节点的地址赋为NULL,以此保证是空节点。

结束。

void init (PSTACK pS)

{

pS->pTop = (PNODE)malloc(sizeof(NODE));


if (NULL == pS->pTop) {

printf("分配内存失败!\n");

exit(-1);

}

else

{

pS->pBottom = pS->pTop;

pS->pTop->pNext = NULL;

}

}

现在初始化以后,S的PTop和PBottom都指向同一个空结点。


push 压栈

创建一个新的结点;

新的节点data存储值;

新的节点的pNext指向栈的pTop;

栈的pTop指向新的节点。

结束。

traverse 遍历

创建一个p节点指针指向栈的最顶部元素;

循环执行:当p节点指针和栈的最底部元素指向地址不同时,打印p节点指针的值,改变p节点指针的指向为下一个节点地址;

结束循环,打印个回车;

结束。

执行一波看看结果。

调用函数。

结果

empty 判断栈是否为空

接受栈的指针作为参数。

判断栈的pTop是不是和栈的pBottom指向相同;

-如果是,返回true;

-如果不是,返回false。

结束

pop 出栈

接受栈的指针、存储出栈的值的整数指针作为参数,

判断栈是否为空(用刚刚的empty方法)

-如果是,出栈失败,返回false;

-如果不是,则继续执行

--定义新节点指针存储当前栈的pTop,新节点指针即为出栈节点

--修改当前栈的pTop指向为pTop的下一个节点(1)

--将出栈节点的指赋值给存储出栈的值的整数指针;(2)

【注】(1)、(2)顺序可变;

--释放出栈节点内存;

--手动将栈定义为空;

--返回true



main调用执行一波

成功时结果如下:


将压栈代码注释,重新跑程序,查看出栈失败场景。

出栈失败时结果如下



clear 清空栈

创建两个节点指针p、q,分别指向pTop、pTop->pNext,这样释放p内存以后仍旧可以找到下一个节点去释放。

这是老师给的方法。


先判断是否为空,为空不执行;

定义p存放pS.pTop;

定义q存放NULL;

当p不等于pS->pBottom,那么不断地free;

循环结束后将pTop指向pBottom。

结束。


栈的内容确实比链表的简单。


5、栈的应用(34_栈的应用 06:58)

函数调用

假设有f、g、y、k这几个函数是这么调用的

f() {g()}

g() {y()}

y() {k()}

k() {}

那么函数执行顺序是这样的

f() -> g() -> y() ->k()

当执行到了k函数以后,又会把值往上面回传

k()-> y() ->  g() -> f()

那么计算机就是用压栈的原理去实现上面的函数调用的过程,仔细一看我们可以发现,当f()内等待g()的值的返回时,f()函数接下来要执行的地址就可以压栈中,以此类推,k函数是最顶部被压入的函数,然后再逐个出栈找到下一个要执行的语句的地址进行执行(将值回传),直到到了栈底的去执行。

中断 - 老师说不讲,但是也很重要(?

表达式求值

3x2+5/6-4


利用两个栈可以编计算器↑

内存分配

缓冲处理

迷宫

以上源代码

https://github.com/D1N910/learn-c/blob/main/haobingDataStructure/30/stack08.c

学习进度(34/63)

继续加油

【D1n910】【郝斌】-数据结构入门学习笔记30-34 栈相关的评论 (共 条)

分享到微博请遵守国家法律