如果你在用scratch编程中遇到什么问题,一直想不出解决的方法,那么建议你认真阅读完这篇文章,或许能使你茅塞顿开,开悟出精彩的解决方案!这就是数据结构的功劳了,Pascal之父---尼古拉斯·沃斯在1984年获得图灵奖提出的著名公式:“算法+数据结构=程序”,就说明了数据结构的重要性,选择适合的数据结构,再加上算法的加持,我们就可以轻松应对各种问题;其实数据结构也是自带光环、自带“灵性”的,好多经典的数据结构都自带算法在其中,也没必要分的那么清!以上为个人愚见,高手勿喷!~
今天这篇,我们来议一下“栈”,本文篇幅较长,可收藏,随时拿出来“品”
本文大纲:
1、栈的概念
2、栈的应用场景
3、用scratch模拟栈的方法
4、样例一:用scratch模拟栈做现场保护
5、样例二:用scratch模拟栈实现表字符串的反转
6、样例三:用scratch模拟栈实现表达式求值
一、“栈”是一个有想法的容器
栈的结构如图所示,有点儿抽象~
我们可以举个栗子:
有一天,有一群傻子排着整齐的队伍在街上走着,一不小心就走进了一条死胡同,这条胡同仅能够容一人进出,那么先进胡同的傻子想要出来,就的等后面的傻子一个一个出去后才能出来,这条胡同其实就是一个“栈”结构。
总结一下,栈(Stack)是一种”先进后出”(First In Last Out)线性存储结构,简称FILO结构;栈的开口端被称为栈顶;相应地,封口端被称为栈底,只能在栈顶进行数据的插入和删除操作。
二、栈的应用栈的应用还是挺广的,以下几个场景是我们经常用的
场景1:浏览器的前进、后退功能
使用双栈stackA 和 stackB,把首次浏览的页面依次压入栈 stackA,当点击后退按钮时,再依次从栈 stackA 中出栈,并将出栈的数据依次放入栈 stackB。当我们点击前进按钮时,我们依次从栈 stackB 中取出数据,放入栈 stackA 中。当栈 stackA 中没有数据时,那就说明没有页面可以继续后退浏览了。当栈 stackB 中没有数据,那就说明没有页面可以点击前进按钮浏览了。
场景2:编辑器撤销与恢复逻辑
其实实现逻辑与浏览器的前进、后退功能一致,也是使用双栈,undo栈表示撤销栈,redo栈表示恢复栈。
1、进行增删改操作时,操作前状态入undo栈;
2、撤销时,当前状态入redo栈,然后undo栈出栈,替换当前状态;
3、恢复时,当前状态入undo栈,然后redo栈出栈,替换当前状态;
4、重新增删改操作时,当前状态入udo栈,恢复栈清空。
如果有撤销步数限制,比如限定20步操作可撤销,需要设定以下umdo栈的容量,需要栈底元素出栈。
场景3:进制转换
举例:把十进制数据转换成二进制,转换的过程实际就是除2取余数,这其中我们可以看到最先求得余数实际是个位数,书写一个数据的时候都是先书写高位的数据,而后依次到个位。这正好和栈后进先出的特性吻合,因此可以使用栈来存储。
场景4:表达式求值
使用两个栈来实现,一个栈用来保存操作数,另一个栈用来保存运算符。从左向右遍历表达式,遇到数字直接压入操作数栈,遇到操作符,就与运算符栈顶元素进行比较。如果比运算符栈顶元素的优先级高,就将当前运算符压入栈;如果比运算符栈顶元素的优先级低或者相同,从运算符栈中取栈顶运算符,从操作数栈的栈顶取 2 个操作数,然后进行计算,再把计算完的结果压入操作数栈,继续比较。
场景5:括号匹配
栈还可以用来检测表达式中的括号是否匹配。
我们假设表达式中只包含三种括号,圆括号 ()、方括号 [] 和花括号{},并且它们可以任意嵌套。比如,{[{}]}或 [{()}([])] 等都为合法格式,而{[}()] 或 [({)] 为不合法的格式。那我现在给你一个包含三种括号的表达式字符串,如何检查它是否合法呢?我们用栈来保存未匹配的左括号,从左到右依次扫描字符串。当扫描到左括号时,则将其压入栈中;当扫描到右括号时,从栈顶取出一个左括号。如果能够匹配,比如“(”跟“)”匹配,“[”跟“]”匹配,“{”跟“}”匹配,则继续扫描剩下的字符串。如果扫描的过程中,遇到不能配对的右括号,或者栈中没有数据,则说明为非法格式。当所有的括号都扫描完成之后,如果栈为空,则说明字符串为合法格式;否则,说明有未匹配的左括号,为非法格式。括号匹配问题的四种结果:(1)左右括号匹配正确 (2)左右括号匹配错误 (3)左括号多于右括号 (4)右括号多于左括号 总结一下,顺序扫描算数表达式(表现为一个字符串),当遇到三种类型的左括号时候让该括号进栈;当扫描到某一种类型的右括号时,比较当前栈顶元素是否与之匹配,若匹配,退栈继续判断;若当前栈顶元素与当前扫描的括号不匹配,则左右括号配对次序不正确;若字符串当前为某种类型的右括号而堆栈已经空,则右括号多于左括号;字符串循环扫描结束时,若堆栈非空(即堆栈尚有某种类型的左括号),则说明左括号多于右括号;
场景6:递归函数
对于每一层递归,函数的局部变量、参数值以及返回地址都被压如栈中。在退回阶段,位于栈顶的局部变量、参数值和返回地址被弹出,用于返回调用层次中执行代码的其余部分,也就是恢复了调用的状态。什么函数要用栈来保存临时变量呢?用其他数据结构不行吗?函数调用的局部状态之所以用栈来记录是因为这些状态数据的存活时间满足“后入先出”(LIFO)顺序,而栈的基本操作正好就是支持这种顺序的访问。
还有好多使用场景,就不再一一列出了~
三、scratch模拟栈
用scratch这门语言的好处是不用过多的考虑语法等问题,而只把关注点集中在问题的实现过程上即可,推荐+1!
栈主要有以下几种基本操作:
(1)push(): 向栈内压入一个成员;
(2)pop(): 从栈顶弹出一个成员;
(3)empty(): 如果栈为空返回true,否则返回false;
(4)top(): 返回栈顶,但不删除成员;
(5)size(): 返回栈内元素的大小
我们在scratch中用列表来模拟栈(用列表尾部作为栈顶),scratch自定义积木没有返回值,用相关变量作为返回值,以上几个功能模块描述如下,其实是几个伪指令罢了~
压栈
出栈
返回栈顶元素
返回栈的大小
四、现场保护功能
由于内螺旋线会破坏当前外螺旋线的坐标和方向,需要提前保护起来,这里就用到了栈这种结构。
ps:scratch自定义模块没有返回值,这里只是模拟栈的现场保护机制,代码有出入!~
五、用栈结构来实现字符串的反转,以此来判断是否是回文数,全文请参看再议“回文”
六、用栈实现表达式求值
使用两个栈来实现,一个栈用来保存操作数,另一个栈用来保存运算符。从左向右遍历表达式,遇到数字直接压入操作数栈,遇到操作符,就与运算符栈顶元素进行比较。如果比运算符栈顶元素的优先级高,就将当前运算符压入栈;如果比运算符栈顶元素的优先级低或者相同,从运算符栈中取栈顶运算符,从操作数栈的栈顶取 2 个操作数,然后进行计算,再把计算完的结果压入操作数栈,继续比较。
学习和交流QQ群:470085939,或添加私人微信(corax_8088)****,****我们一起scratch!