当前位置: 代码迷 >> 综合 >> 【数据结构与算法之美】栈:如何实现浏览器的前进和后退功能?
  详细解决方案

【数据结构与算法之美】栈:如何实现浏览器的前进和后退功能?

热度:76   发布时间:2024-01-27 00:28:28.0

一、如何理解“栈”?

1、栈是一种操作受限的数据结构,只支持入栈和出栈操作。
2、典型的“栈”结构:后进者先出,先进者后出。
3、从栈的操作特性上来看,栈是一种“操作受限”的线性表,只允许在一端插入和删除数据。
4、特定的数据结构是对特定场景的抽象,而且,数组或链表暴露了太多的操作接口,操作上的确灵活自由,但使用时就比较不可控,自然也就更容易出错。

二、如何实现一个“栈”?

1、栈既可以用数组来实现,也可以用链表来实现。用数组实现的栈,叫作顺序栈,用链表实现的栈,叫作链式栈。
2、不管基于数组还是链表,入栈、出栈的时间复杂度都为 O(1)。
不管是顺序栈还是链式栈,存储数据只需要一个大小为 n 的数组就够了。
在入栈和出栈过程中,只需要一两个临时变量存储空间,所以空间复杂度是 O(1)。
入栈、出栈只涉及栈顶个别数据的操作,所以时间复杂度都是 O(1)。

三、支持动态扩容的顺序栈

1、如果要实现一个支持动态扩容的栈,我们只需要底层依赖一个支持动态扩容的数组就可以了。当栈满了之后,我们就申请一个更大的数组,将原来的数据搬移到新数组中。
2、出栈的时间复杂度是 O(1)。
入栈操作,最好情况时间复杂度是 O(1),最坏情况时间复杂度是 O(n)。均摊时间复杂度为O(1)。

四、栈在函数调用中的应用

1、经典应用场景:函数调用栈。
2、操作系统给每个线程分配了一块独立的内存空间,这块内存被组织成“栈”这种结构, 用来存储函数调用时的临时变量。每进入一个函数,就会将临时变量作为一个栈帧入栈,当被调用函数执行完成,返回之后,将这个函数对应的栈帧出栈。

五、栈在表达式求值中的应用

1、常见的应用场景,编译器如何利用栈来实现表达式求值。比如:34+13*9+44-12/3。
2、编译器就是通过两个栈来实现的。其中一个保存操作数的栈,另一个是保存运算符的栈。
从左向右遍历表达式,当遇到数字,我们就直接压入操作数栈;当遇到运算符,就与运算符栈的栈顶元素进行比较。
如果比运算符栈顶元素的优先级高,就将当前运算符压入栈;如果比运算符栈顶元素的优先级低或者相同,从运算符栈中取栈顶运算符,从操作数栈的栈顶取 2 个操作数,然后进行计算,再把计算完的结果压入操作数栈,继续比较。

六、栈在括号匹配中的应用

1、借助栈来检查表达式中的括号是否匹配。比如,{[{}]}或 [{()}([])] 等都为合法格式,而{[}()] 或 [({)] 为不合法的格式。
2、用栈来保存未匹配的左括号,从左到右依次扫描字符串。当扫描到左括号时,则将其压入栈中;当扫描到右括号时,从栈顶取出一个左括号。如果能够匹配,比如“(”跟“)”匹配,“[”跟“]”匹配,“{”跟“}”匹配,则继续扫描剩下的字符串。如果扫描的过程中,遇到不能配对的右括号,或者栈中没有数据,则说明为非法格式。
当所有的括号都扫描完成之后,如果栈为空,则说明字符串为合法格式;否则,说明有未匹配的左括号,为非法格式。

七、如何实现浏览器的前进、后退功能?

1、使用两个栈,X 和 Y,把首次浏览的页面依次压入栈 X,当点击后退按钮时,再依次从栈 X 中出栈,并将出栈的数据依次放入栈 Y。当点击前进按钮时,依次从栈 Y 中取出数据,放入栈 X 中。当栈 X 中没有数据时,那就说明没有页面可以继续后退浏览了。当栈 Y 中没有数据,那就说明没有页面可以点击前进按钮浏览了。

八、课后思考

1. 为什么函数调用要用“栈”来保存临时变量呢?用其他数据结构不行吗?

解答:因为函数调用的执行顺序符合后进者先出,先进者后出的特点。比如函数中的局部变量的生命周期的长短是先定义的生命周期长,后定义的生命周期短;还有函数中调用函数也是这样,先开始执行的函数只有等到内部调用的其他函数执行完毕,该函数才能执行结束。
正是由于函数调用的这些特点,根据数据结构是特定应用场景的抽象的原则,我们优先考虑栈结构。

2.JVM 里面的“栈”跟我们这里说的“栈”是不是一回事呢?如果不是,那它为什么又叫作“栈”呢?

解答:内存中的堆栈和数据结构堆栈不是一个概念,可以说内存中的堆栈是真实存在的物理区,数据结构中的堆栈是抽象的数据存储结构,但二者都遵循”后进先出“的规则。

1)内存中的堆栈
内存空间在逻辑上分为三部分:代码区、静态数据区和动态数据区,动态数据区又分为栈区和堆区。

代码区:存储方法体的二进制代码。高级调度(作业调度)、中级调度(内存调度)、低级调度(进程调度)控制代码区执行代码的切换。
静态数据区:存储全局变量、静态变量、常量,常量包括final修饰的常量和String常量。系统自动分配和回收。
栈区:存储运行方法的形参、局部变量、返回值。由系统自动分配和回收。
例如 int method(int a){int b;}栈中存储参数a、局部变量b、返回值temp。
堆区:new一个对象的引用或地址存储在栈区,指向该对象存储在堆区中的真实数据。由程序员分配和回收(Java中由JVM虚拟机的垃圾回收机制自动回收)。
例如 Class Student{int num; int age;} main方法中Student stu = new Student();分配堆区空间中存储的该对象的num、age,变量stu存储在栈中,里面的值是对应堆区空间的引用或地址。

2)数据结构中的堆栈
栈:遵循”后进先出“规则,根据存放数据的需求,可自己实现,也可以调用标准库中的实现。

堆:(二叉)堆是一种数组对象,可视为一棵完全二叉树(跟二叉树的实现不同),树的每一层都是满的(最后一层可能除外)。二叉堆有最大堆(根最大)和最小堆(根最小)。最大堆一般用在堆排序算法中,最小堆通常在构造优先队列时使用。

九、leetcode上关于栈的题目学习

打开leetcode搜索题号:20,155,232,844,224,682,496.