数据结构自学笔记二、栈

  • 栈的定义
  • 栈的作用
  • 栈的顺序存储与随机存储
  • 栈的基本功能的实现
    • 先以顺序存储结构的栈为例。
    • 然后再说说随机存储结构的栈吧
  • 两栈共享空间
  • 栈的一个重要应用——四则运算
    • 算式的后缀表示法
    • 如何计算后缀表达式得到结果
  • 写在后面的话

(温馨提示:本人是一名正在自学数据结构的小白,如果你是想要得到某个疑问的答案,请另觅高就,不要别我浅薄的学识耽误了)

栈的定义

刚刚我们学了线性表,其实栈在也可以算作是线性表,甚至在进行某些操作的时候要比之前讲的一般的线性表更为简单。

栈是只能在一端进行操作的线性表

小学的时候很喜欢玩《穿越火线》,当时玩终结者模式,一堆人钻在一条狭窄的胡同里,因为幽灵只能从一端进攻,所以只要火力充足,基本都能苟到最后。
但有一个问题,就是胡同太窄了,大家只能排成一列,所以如果前面的人不出去,后面的人也出不去。栈就是像这样的一种数据结构,它是一种线性表,但是只能从线性表的一段进行操作,如添加元素(压入栈push)和删除元素(弹出栈pop)等。

栈中元素的特性是先进后出,后进先出。我们把栈可以进行操作的一端称为栈顶,另一端称为栈底。用户只能对栈顶的元素进行操作。

栈的作用

可能初学者会和我一样想:栈到底有什么用?它是线性表,却比线性表多了很多约束条件,反而不能自由的处理它的任意一个元素,这不是变麻烦了吗?
且慢,我们来看看栈有什么用吧。

我相信很多人在学C语言的函数模块时,一定做过这样一个题:输出斐波那契数列的前多少多少个。
比如:请输出斐波那契数列的前10个数。
而且这个题一定在学循环的时候出现一次,在学函数的时候又会出现一次。
如果我们用循环来写,可以这样:

 int a[10];//由于只输出十个,我们可以声明数组来存放a[0]=a[1]=1;cout<<a[0]<<endl<<a[1];//输出前两个for(int i=2;i<10;i++)//循环八次{a[i]=a[i-1]+a[i-2];cout<<a[i];//输出数字}

当然,这并不是一个非常睿智的写法,因为它需要开数组。用两个变量进行循环同样可以达到目的,聪明的你一定能够想到,此处不赘述。
但是,在我们学函数模块时,老师会鼓励我们设计一个递归函数,请看代码:

 int Fibonacci(int n)//一个整型函数,传入一个int型n,返回斐波那契数列的第n个数{if(n==1||n==2)return 1;else return Fibonacci(n-1)+Fibonacci(n-2);}

这个代码就非常的简洁而明智。因为C语言是允许函数调用其他函数的(包括自身),这样递归调用函数实现了代码的简化。
这和栈有什么关系吗?

别急,再讲一个故事。还是初学C语言的时候,做了这么一个题:输出斐波那契数列的第10000个数。当时我自信满满的用了刚学的递归,写了一个函数,结果一运行,编译器就报错了。
我蒙了,求救大佬室友。大佬瞅了一眼,“哦,爆栈了,你用循环做吧”
爆栈指的就是函数栈溢出,函数栈用到的就是栈这种数据结构,函数对函数的调用是通过函数栈来完成的。
我们之前提到,栈的特性是后进先出。而程序运行过程中,运行函数时会将函数压入函数栈,在函数运行结束得到返回值后再弹出;如果函数运行没有结束而是调用了另外一个函数,比如像递归函数这样,那么新调用的函数会继续压入函数栈中。如果递归层数太多,也就是说压入函数栈的函数就越多。函数栈的容量是有限的,当压入函数栈的函数达到一定量时,就会发生“溢出”,此时编译器就会抛出一个错误的提示。(听不太明白,别急,我们待会要学到的栈的一些基本操作会解释这一点)
(感兴趣的可以看一看这篇博客:link)

我们可以看到,在递归的过程中,栈的这种先进后出的特性与函数的调用过程十分契合,这就是栈的一个重要用途——用于递归过程。
此外,由于栈的特性,我们只能对栈顶的一个元素进行处理,这种局限性反而缩小了我们思考的范围,让我们可以把精力集中在单个的问题上,而不去考虑其他的元素。这也简化了程序设计。

在介绍完栈的一些基本功能的实现后,我们会再着重讲讲如何用栈实现四则运算,相信届时你会更加理解栈的作用。

栈的顺序存储与随机存储

栈也是线性表的一种,因此同样有顺序存储和随机存储两种类型。优劣也基本相同。
随机存储的栈又叫链栈。

栈的基本功能的实现

先以顺序存储结构的栈为例。

1.初始化,即生成一个新栈

我们先想想一个栈需要什么。首先,要可以存放数据。顺序存储结构的话,就是定义一个数组,我们不妨设为整型数组,长度为10。此外,我们还需要一个top“指针”,告诉我们栈顶的位置。因为是数组,我们只需要一个整型数来记录栈顶元素在数组中的下标即可。
初始化时,将top默认设为-1,每存入一个元素,则+1;

 struct stack{//定义一个结构体为栈int data[10];//也可以是其他长度int top;//记录栈顶元素的下标};int main()//初始化一个栈{stack Stack01;Stack01.data[0]=0;top++;}

2.消除一个栈
由于顺序存储结构的栈占用空间是预先定义好的,所以这个操作不是很需要

3.将栈清空
从top开始,将栈结构体中数组的元素全部改为0

void clear_stack(stack* Stack01ptr,int top)
{while(top>=0){Stack01ptr->data[top--]=0;}
}

4.判断栈是否为空
直接看top的值不就好了?

5.若栈非空,则返回栈顶元素

int get_elem(stack* Stack01ptr,int top)
{if(top<0)return ERROR;else return Stack01ptr->data[top];
}

6.为栈添加元素

诶嘿,注意这里要判断栈是否满了,否则会发生栈溢出哦

void push(stack* Stack01ptr,int top,int newdata)//假设栈长度为10
{if(top==9)return ERROR;else{top++;Stack01ptr->data[top]=newdata;}
}

7.为栈删除元素

诶嘿,这里要判断栈是否为空

void pop(stack* Stack01ptr,int top)//如果需要知道弹出的元素的值,可以设返回值
{if(top==-1)return ERROR;else{Stack01ptr->data[top--]=0;}
}

8.返回栈的元素个数

好办!告诉他top+1等于多少就完事了

然后再说说随机存储结构的栈吧

1.初始化,即生成一个新栈

与线性表相同的,我们需要在顺序存储结构的栈的结构体中加入指针域

 struct stack{//定义一个结构体为栈int data;//也可以是其他长度stack* next;//记录栈顶元素的下标};

但是与线性表不同的,由于栈是后进先出,所以栈的“头结点”(只是拿来类比,并不是这么称呼)时刻在变化。
有了线性表的基础,我们很容易写出以下代码

 int main()//初始化一个栈{stack Stack01;Stack01.data=0;Stack01.next=NULL;int tempdata;stack *p;while(cin>>tempdata){p=(stack*)malloc(sizeof(stack));p->data=tempdata;********}}

诶,你这代码怎么不全啊?
真不怪我,********处应该是让新的节点的指针指向上一个节点,然后把top指针指向最新的节点。也许你发现了,是不是还没有定义top指针?

由于不是顺序存储结构了,我们如果要找到节点位置就必须靠指针了,但是如果光靠一个指针,我们要得到栈的长度,岂不是又要遍历?干脆,我们构造一个结构体,让它即有top指针,又有栈的长度!

\\定义一个含top指针和栈长度length的结构体
struct sym_stack{stack *top;int len_stack;
};
\\接上之前*********省略的代码,在主函数中声明这个结构体并让top指向第一个节点的代码略p->next=sym_stack01->top;sym_stack01->top=p;sym_stack01->len_stack++;

2.消除一个栈
emmmm,我觉得和添加元素算法差不多,记得用free就行了嗷

3.将栈清空
从top开始,将栈结构体中数组的元素全部改为0。但是因为你还要找到这个栈,所以top指针不能移动,让一个新的指针取top的值再进行遍历

void clear_stack(stack* Stack01ptr,sym_stack *sym_stack01,int len_stack)
{while(len_stack--){sym_stack01->topnow->data=0;sym_stack01->topnow=sym_stack01->topnow->next;}
}

4.判断栈是否为空
直接看len_stack的值不就好了?

5.若栈非空,则返回栈顶元素

int get_elem(sym_stack *sym_stack01)
{if(sym_stack01->len_stack==0)return ERROR;else return sym_stack->top->data;
}

6.为栈添加元素

void push(sym_stack *sym_stack01,int newdata)
{sym_stack01->len_stack++;stack *newstack=(stack*)malloc(sizeof(stack));newstack->data=newdata;newstack->next=sym_stack01->top;sym_stack01->top=newstack;
}

7.为栈删除元素

诶嘿,这里要判断栈是否为空

void push(sym_stack *sym_stack01)
{if(sym_stack01->len_stack==0)return ERROR;else{stack *tempptr=sym_stack01->top->next;free(sym_stack01->top);sym_stack01->top=tempptr;}
}

8.返回栈的元素个数

好办!告诉他len_stack等于多少就完事了

两栈共享空间

emmm,在介绍如何用栈实现四则运算之前,我们介绍一种可以节约空间的做法——两栈共享空间。
这个做法主要适用于顺序存储结构的栈,毕竟链栈基本不需要担心空间不够这个问题嘛

大致做法就是,将两个栈放入一个数组中,一个top初始为-1,另一个为数组长度;前者栈没压入一个元素就++,后者–;当两者相差1时,栈满。其他操作基本只需要小修改就可以照搬使用。

这种两栈共享空间的做法,一般用于:1、两栈的数据类型相同;2.两栈存放的元素间存在负相关关系,即这个多另一个就会相应减少,比如两者零和博弈。

栈的一个重要应用——四则运算

怎么说呢,我常常感慨有一些东西前辈们是如何想到的,而这个用栈实现计算机的四则运算就是我很惊讶的一点。

算式的后缀表示法

给一个式子:(89+111)/(240/24),让计算机去算,它会吗?
是的,它会!
但是呢,它并不是直接计算这个式子,而是把这个式子由我们熟悉的中缀表达式换成后缀表达式
转换的原则:从左到右遍历表达式的数字和运算符,如果是数字,直接输出,如果是**(或者是运算符,但是其运算优先级高于栈顶的运算符或者栈顶是(,则压入栈中;如果是运算符且运算优先级不高于栈顶的运算符,则直接输出;如果是),就将栈顶元素一个个弹出,直到弹出与之匹配的(**为止,不过括号不输出到后缀表达式中。

我们来实操一下吧!
s1.栈:(
表达式:无
s2.栈:(+
表达式:89
s3.栈:(+)
表达式:89 111
s4.栈:/(
表达式:89 111 +
s5.栈:/(/
表达式:89 111 + 240
s6.栈:/(/)
表达式:89 111 + 240 24
s7.栈:/
表达式:89 111 + 240 24 /
s8.栈:
表达式:89 111 + 240 24 / /

最终,我们得到原式的后缀表达式为:89 111 + 240 24 / /

如何计算后缀表达式得到结果

后缀表达式的计算原则是:遇到数字就进栈,遇到运算符就弹出两个数字进行该运算,如果是减法和除法,先弹出的作为减数和除数,后弹出的作为被减数和被除数。得到的结果再压入栈中。
好,89,111,进栈;
读到+,弹出89和111,做加法,得到200压入栈中
240,24,进栈;
读到/,弹出240和24,做除法,得到10,进栈;
读到/,弹出10和200,做除法,得到20,进栈;
读完了,弹出20,结果即为20!
此外,你还可以根据运算法则,拓展更广阔的计算!

栈多有趣呀~

写在后面的话

其实我在大一下学期自学C++的时候,就稍微接触了栈,当时mooc上是在讲如何用类模板构造一个栈,不过当时学业压力紧再加上宅家没心思学习,所以听得不很认真。
当时其实有一个很大的疑惑:就是栈到底有什么用?确实,到现在,我也只接触了递归和四则运算这两个用到了栈的实例,哦,突然想起来之前学C的时候有一个检查括号匹配的题目应该也可以用到栈。但是,如果只是这些小小的地方用到栈,栈似乎……没多大用处?
不过现在有点想明白了,就像数组、链表一样,栈只是一种数据结构,它适用于一切需要用到它先进后出的特性的情况。现在觉得没啥用,只是我还没有见到足够多的情况。
这么说来,每一种数据结构都有其独到的用法。我开始自学两天了,分别学了线性表和栈,接下来还有队列、图、树这些在等着我,就像卡池里还有无数SSR在等着我去捞……这样想想,还有些小激动呢。
最后,还是得说一句,看我的博客也就图一乐,真要学知识,还是多看看大牛的博客,或者买本教材系统的学吧。

数据结构自学笔记二、栈相关推荐

  1. c语言中缀表达式求值_数据结构考研笔记之栈与队列(四)栈与队列应用括号匹配、中缀表达式转前缀后缀问题...

    文字:独木 排版:独木 图片:独木 栈与队列 1.括号匹配问题 栈 例题1 例题2-----不匹配例题1 例题3-----不匹配例题2 2. 表达式求值问题 例题 1.中缀表达式转前缀表达式 2.中缀 ...

  2. 【代码随想录】【LeetCode】自学笔记07 - 栈和队列

    总结 基础补牢:[https://blog.csdn.net/tham_/article/details/44733101] 根据[http://c.biancheng.net/view/3354.h ...

  3. 数据结构实验报告二 栈和队列

    一.实验目的 1.掌握栈的结构特性及其入栈,出栈操作: 2.掌握队列的结构特性及其入队.出队的操作,掌握循环队列的特点及其操作. 二.实验内容和要求 1.阅读下面程序,将函数Push和函数Pop补充完 ...

  4. 数据结构与算法(二) 栈与队列(代码示例)

    数据结构与算法 栈与队列 1. 数组和链表实现栈 2. 用O(1)的时间复杂度求栈中的最小元素 3. 链表和数组实现队列 4. 用两个栈模拟队列操作 1. 数组和链表实现栈 链表的方式: /*** 描 ...

  5. 大话数据结构学习笔记二:算法

    一 算法定义 算法是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作. 二 算法的特性: 1 输入输出:算法具有零个或者多个输入,至少有一个或者多个输出. 2 ...

  6. MySQL自学笔记(二)

    二.数据库管理 2.1 外键(FOREIGN KEY) MySQL 外键约束(FOREIGN KEY)用来在两个表的数据之间建立链接,它可以是一列或者多列.一个表可以有一个或多个外键. 外键对应的是参 ...

  7. 数据结构自学笔记(C语言)十大排序

    这节讲排序,排序有十大经典算法,如图所示.(概念参考https://www.cnblogs.com/onepixel/p/7674659.html) 十种常见排序算法可以分为两大类: 比较类排序:通过 ...

  8. Docker自学笔记 (二)

    容器数据卷 什么是容器数据卷 将应用 和环境打包成一个镜像! 如果数据都在容器中,容器一删除数据就会丢失.需求:数据持久化 容器之间可以用一个数据共享的技术!Docker 容器中产生的数据,同步到本地 ...

  9. Photoshopcs6 自学笔记二 图像处理

    图像处理操作 污点修复工具 快捷键 j 按 [ 键缩小画笔范围,按 ]键放大画笔范围. 污点修复工具可以抹去图片要删除的地方. 修复画笔工具 修复如下图的时候可以使用修复画笔工具 按住alt键获取到源 ...

最新文章

  1. R语言plotly可视化:可视化直方图、归一化的直方图、水平直方图、互相重叠的直方图、堆叠的直方图、累积直方图、通过bingroup参数设置多个直方图使用相同的bins设置、自定义直方图条形的间距
  2. Android Studio 常用快捷键分类整理
  3. ai怎么画循环曲线_科研论文作图系列-从PPT到AI (三)
  4. JAVA实现输入一个整数,输出该数二进制表示中1的个数(《剑指offer》)
  5. java %3c%=a%%3e_跪求帮忙解析,急!!!
  6. CCPC-Wannafly Comet OJ 夏季欢乐赛(2019)E
  7. 企业信息管理- 近期功能改善
  8. 浏览器汇总介绍--Opera
  9. Linux下C++可视化调试神器vgdb
  10. 论文笔记_S2D.19_2018-PR_基于膨胀卷积神经网络与软加权和推理的分层融合单目深度估计
  11. 【bzoj2654】tree(二分+MST)
  12. 有很多种方法来解决八数码
  13. [单片机]KeilC51简单流水灯制作与原理
  14. 百度市值要被京东超越了?你投百度还是京东?
  15. 一位acm过来人的心得
  16. 03-STM32+Air724UG远程升级篇OTA(阿里云物联网平台)-STM32+Air724UG使用阿里云物联网平台OTA远程更新STM32程序
  17. uniapp中页面白屏问题
  18. 电磁场仿真软件ANSYS Electronics下载附安装教程
  19. 印象笔记目录导出(失败)
  20. 不同需求下,CPU怎么选?

热门文章

  1. word2vec 词向量
  2. IE和firefox上传文件mime类型的设置
  3. 鸿蒙抖音社区下单,抖音鸿蒙封神录(礼包领取)-抖音鸿蒙封神录官网版下载v1.0.5 - 比克尔下载...
  4. 热风枪使用之碳化温度
  5. java: JDK isn‘t specified for module ‘maven-junit41‘解决办法
  6. 盘企lcms php开发框架,LCMS操作 · 盘企LCMS PHP开发框架文档 · 看云
  7. mysql中新建数据库create table的COLLATE是什么?
  8. CRM系统的销售管理
  9. 乐观型人格分析,性格乐观的优缺点和职业发展分析
  10. Python - 在for循环体内修改i值