栈:如何实现浏览器的前进和后退功能?

2019-08-26 05:54:59来源:博客园 阅读 ()

新老客户大回馈,云服务器低至5折

栈:如何实现浏览器的前进和后退功能?

栈是什么? 想象是一摞叠在一起的盘子,在放盘子的时候,需要自下而上一个一个放,取盘子的时候需要自上而下一个一个取。 典型的栈结构:先进者后出,后进者先出,是一种操作受限的数据接口,只能在一端进行插入和删除操作。   栈主要包含两个操作,主要是入栈和出栈(插入和读取并删除)操作。 栈既可以用数组实现,也可以用链表实现,用数组实现的栈称为顺序栈,用链表实现的栈称为链式栈。 顺序栈代码: // 基于数组实现的顺序栈 public class ArrayStack {   private String[] items;  // 数组   private int count;       // 栈中元素个数   private int n;           // 栈的大小     // 初始化数组,申请一个大小为 n 的数组空间   public ArrayStack(int n) {     this.items = new String[n];     this.n = n;     this.count = 0;   }     // 入栈操作   public boolean push(String item) {     // 数组空间不够了,直接返回 false,入栈失败。     if (count == n) return false;     // 将 item 放到下标为 count 的位置,并且 count 加一     items[count] = item;     ++count;     return true;   }      // 出栈操作   public String pop() {     // 栈为空,则直接返回 null     if (count == 0) return null;     // 返回下标为 count-1 的数组元素,并且栈中元素个数 count 减一     String tmp = items[count-1];     --count;     return tmp;   } } 时间复杂度是O(1) 空间复杂度也是O(1)   支持动态扩容的顺序栈: 之前我们实现顺序栈使用的底层结构是数组,不支持动态扩容,事实上只要将数组替换成一个可以动态扩容的数组就可以了,比如java中的list 分析一下动态扩容顺序栈的时间复杂度和空间复杂度: 出栈的时间复杂度依然是O(1),因为不涉及到数组扩容与数据搬迁工作 入栈时间复杂度,最好的情况下是O(1)(空间足够时),最坏的情况是O(n)(空间不足时,复制和搬迁数据)。 使用摊还分析法来分析时间复杂度 : 假设栈大小是k,在入栈过程中,当栈容量不足时,下一次入栈操作会触发一次内存申请操作,以及k个数据的搬迁操作,在之后的k-1次入栈操作,都不需要再重新申请内存以及搬迁数据,将搬迁数据均摊到入栈操作,可以得出入栈操作整体的时间复杂度接近O(1)   栈在函数中的应用 典型场景---函数调用栈 操作系统给每个线程都分配了一块独立的内存空间,这种内存空间被组织成栈这样的结构,用来存储函数调用时的临时变量。没进入一个函数,就会将临时变量作为一个栈帧入栈,调用函数完成,返回之后,将函数对应的栈帧出栈。   栈在表达式求值中的使用: 以一个简单运算为例:3+5*8-6=? 编译器通过两个栈来实现,一个 存储操作数的栈,另一个存储运算符的栈。从左向右遍历表达式,遇到数字压入操作数栈,遇到运算符,与运算符栈的栈顶数据比较。 如过优先级高于运算符栈栈顶元素,就将运算符入栈,如果低于,就将栈顶元素取出,并且从操作数栈中取出两个元素进行运算,将计算结果压入操作数栈。   栈在括号匹配中的应用: 用栈保存为匹配的左括号,从左到右一次扫描字符串,当扫描到左括号时,则将其压入栈中;当扫描到右括号时,从栈顶取出一个左括号,如果能匹配上,则继续扫描剩下的字符串。如果扫描过程中,遇到不能配对的右括号,或者栈中没有数据,则说明为非法格式。   如何实现浏览器的前进后退功能? 我们使用两个栈X和Y,我们把首次浏览的页面依次压如栈X,当点击后退按钮时,再依次从栈X中出栈,并将出栈的数据一次放入Y栈。当点击前进按钮时,我们依次从栈Y中取出数据,放入栈X中。当栈X中没有数据时,说明没有页面可以继续后退浏览了。当Y栈没有数据,那就说明没有页面可以点击前进浏览了。   思考: 1.我们在讲栈的应用时,讲到用函数调用栈来保存临时变量,为什么函数调用要用“栈”来保存临时变量呢?用其他数据结构不行吗? 其实,我们不一定非要用栈来保存临时变量,只不过如果这个函数调用符合后进先出的特性,用栈这种数据结构来实现,是最顺理成章的选择。 从调用函数进入被调用函数,对于数据来说,变化的是什么呢?是作用域。所以根本上,只要能保证每进入一个新的函数,都是一个新的作用域就可以。而要实现这个,用栈就非常方便。在进入被调用函数的时候,分配一段栈空间给这个函数的变量,在函数结束的时候,将栈顶复位,正好回到调用函数的作用域内。   2.我们都知道,JVM 内存管理中有个“堆栈”的概念。栈内存用来存储局部变量和方法调用,堆内存用来存储 Java 中的对象。那 JVM 里面的“栈”跟我们这里说的“栈”是不是一回事呢?如果不是,那它为什么又叫作“栈”呢? 内存中的堆栈和数据结构堆栈不是一个概念,可以说内存中的堆栈是真实存在的物理区,数据结构中的堆栈是抽象的数据存储结构。 代码区:存储方法体的二进制代码。高级调度(作业调度)、中级调度(内存调度)、低级调度(进程调度)控制代码区执行代码的切换。 静态数据区:存储全局变量、静态变量、常量,常量包括final修饰的常量和String常量。系统自动分配和回收。 栈区:存储运行方法的形参、局部变量、返回值。由系统自动分配和回收。 栈区:存储运行方法的形参、局部变量、返回值。由系统自动分配和回收。  

原文链接:https://www.cnblogs.com/wangzepu/p/11368595.html
如有疑问请与原作者联系

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇:Java JDBC

下一篇:Java基础—内部类