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

news2024/11/16 13:48:54

文章来源于极客时间前google工程师−王争专栏。

如何理解栈

后进者先出,先进者后出,这就是典型的“栈”结构。

从栈的操作特性来看,栈是一种“操作受限”的线性表,只允许在一端插入和删除数据。

当某个数据集合只涉及在一端插入和删除数据,并且满足后进先出、先进后出的特性,我们就应该首选“栈”这种数据结构。

栈既可以用数组来实现,也可以用链表来实现。用数组实现的栈,我们叫做顺序栈,用链表实现的栈,我们叫做链式栈。

public class ArrayStack{
    //内部维护一个数组
    private String[] items;
    //栈中元素个数
    private int count;
    //栈的大小
    private int 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;
        items[count] = item;
        ++count;
        return true;
    }
    //出栈操作
    public String pop(){
        if(count == 0) return null;
        //返回下标为count-1的数组元素
        String tmp = items[count-1];
        --count;
        return tmp;
    }
}
  • 空间复杂度为O(1),注意大小为n的数组,并不是说空间复杂度就是O(n)。
  • 时间复杂度为O(1)

支持动态扩容的顺序栈

上述基于数组实现的栈,是一个固定大小的栈。链式栈的大小不受限,但要存储next指针,内存消耗相对较多。

基于数组可以设计动态扩容的顺序栈

image

复杂度分析

  • 出栈,不设计内存的重新申请和数据的搬移,出栈时间复杂度依然为O(1)。
  • 入栈,有空闲空间为O(1),空间不够为O(n)
    • 最好O(1)
    • 最坏O(n)
  • 入栈均摊分析(需要前提假设)
  1. 栈空间不够时,我们重新申请一个是原来大小两倍的数组。
  2. 为了简化分析,假设只有入栈,没有出栈操作。
  3. 定义不涉及内存搬移的入栈操作为simple-push操作,时间复杂度为O(1)。
    image
    入栈操作的均摊时间复杂度为O(1)

栈在函数调用中的应用

比较经典的一个应用场景就是函数调用栈

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

int main(){
    int a = 1;
    int ret = 0;
    int res = 0;
    ret = add(3,5);
    printf("%d",res);
    return 0;
}
int add(int x,int y){
    int sum = 0;
    sum = x+y;
    return sum;
}

image

栈在表达式求值中的应用

编译器利用栈来实现表达式求值。
简单表达式,比如34+13*9+44-12/3
编译器通过两个栈来实现。
一个保存操作数的栈。一个保存运算符的栈。

  • 遇到数字,直接压入操作数栈
  • 遇到运算符,就与运算符栈的栈顶元素进行比较
    • 如果比栈顶的优先级高,就将当前运算符压入栈。
    • 如果比栈顶的优先级低或者相同。从运算符栈顶取运算符,从操作数栈的栈顶取2个操作数,然后进行计算,再把计算完的结果压入操作数栈,继续比较。
      image

栈在括号匹配中的应用

除了用栈来实现表达式求值,我们还可以借助栈来检查表达式中的括号是否匹配。
{[{}]}或[{()}([])]都为合法格式,而{[}()]或[({)]为不合法格式。如何检查合法性呢?

我们用栈来保存未匹配的左括号,从左到右依次扫描字符串。当扫描到左括号时,则将其压入栈中;当扫描到右括号时,从栈顶取出一个左括号。能匹配,继续扫描。不能匹配,或者栈中没有数据,则说明为非法格式。

解答开篇

当访问一串页面a-b-c之后,点击浏览器的后退按钮,就可以查看b和a。后退到a,点击前进可以查看b和c。如果后退到a,点击新的页面d,就无法查看b和c了。

如何实现浏览器的前进、后退功能?用两个栈可以完美解决。

  1. 顺序查看a,b,c,依次压入栈
    image
  2. 通过浏览器后退按钮,从页面c后退到页面a之后,依次将c和b从栈X弹出,入栈Y。
    image
  3. 这个时候又想看b,b出栈Y,入栈X。
    image
  4. 这个时候,通过页面b又跳转到新的页面d,页面c就无法再通过前进、后退按钮重复看了,所以需要清空栈Y。
    image

内容小结

  1. 栈是一种操作受限的数据结构,只支持入栈和出栈操作。
  2. 后进先出是它最大的特点。
  3. 栈可以通过数组实现,也可以通过链表实现。入栈、出栈时间复杂度都是O(1)。
  4. 支持动态扩容的顺序栈,需要重点掌握均摊时间复杂度方法。

思考

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

其实,我们不一定非要用栈来保存临时变量,只不过如果这个函数调用符合后进者先出的特性,用栈这种数据结构来实现,是最顺理成章的选择。

从调用函数进入被调用函数,对于数据来说,变化的是什么呢?是作用域。所以根本上,只要能保证每进入一个新的函数,都是一个新的作用域就可以。而要实现这个,用栈就非常方便。在进入被调用函数的时候,分配一段栈空间给这个函数的变量,在函数结束的时候,将栈顶复位,正好回到调用函数的作用域内。

  1. JVM内存管理有个“堆栈”的概念。栈内用来存储局部变量和方法调用,堆内用来存储java中的对象。那JVM里面的“栈”跟我们这里说的“栈”是不是一回事呢?如果不是,那它为什么又叫作“栈”呢?

内存中的堆栈是真实存在的物理区,数据结构中的堆栈是抽象的数据存储结构。内存空间在逻辑上分为三部分:代码区、静态数据区和动态数据区,动态数据区又分为栈区和堆区。
代码区:存储方法体的二进制代码。高级调度(作业调度)、中级调度(内存调度)、低级调度(进程调度)控制代码区执行代码的切换。
静态数据区:存储全局变量、静态变量、常量,常量包括final修饰的常量和String常量。系统自动分配和回收。
栈区:存储运行方法的形参、局部变量、返回值。由系统自动分配和回收。
堆区:new一个对象的引用或地址存储在栈区,指向该对象存储在堆区中的真实数据。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1067093.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【yolov系列:yolov7改进添加SIAM注意力机制】

yolo系列文章目录 文章目录 yolo系列文章目录一、SimAM注意力机制是什么?二、YOLOv7使用SimAM注意力机制1.在yolov7的models下面新建SimAM.py文件2.在common里面导入在这里插入图片描述 总结 一、SimAM注意力机制是什么? 论文题目:SimAM: A …

使用访问图像三种办法,指针,迭代器,动态地址计算

使用访问图像三种办法&#xff0c;指针&#xff0c;迭代器&#xff0c;动态地址计算 指针访问 方法一 #include <opencv2/opencv.hpp> #include <iostream> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp>using n…

TS中interface接口的使用

接口用来定义一个类的结构&#xff0c;用来定义一个接口中应该包含哪些属性和方法 语法结构如下&#xff1a; interface 接口名 { // 属性和方法 } 一、使用接口进行类型声明 我们声明一个对象类型可以使用如下方法&#xff1a; // 定义一个对象类型 type Mytype {name: st…

show index 中部分字段的含义

show index from 表名 查看某张表的索引情况 另: SELECT * FROM information_schema.STATISTICS WHERE TABLE_NAME "t1" 与 show index from t1 作用相似,且会返回更多的字段信息 创建一张测试表t1: CREATE TABLE t1 ( id INT ( 11 ) NOT NULL AUTO_INCREMENT, nam…

【C++】STL详解(十二)—— 用哈希表封装出unordered_map和unordered_set

​ ​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;C学习 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 上一篇博客&#xff1a;【C】STL…

Zookeeper 集群搭建

Zookeeper Zookeeper是一个开源的分布式的&#xff0c;为分布式框架提供协调服务的Apache项目 Zookeeper 工作机制 Zookeeper从设计模式角度来理解&#xff1a;是一个基于观察者模式设计的分布式服务管理框架 一旦这些数据的状态发生变化&#xff0c;Zookeeper就将负责通知…

【EI复现】基于同步发电机转动惯量和阻尼系数协同自适应控制策略(Simulink仿真实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

华为OD机试 - 计算最大乘积(2022Q4 100分)

目录 专栏导读一、题目描述二、输入描述三、输出描述四、解题思路五、Java算法源码六、效果展示1、输入2、输出3、说明 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷&#…

分贝定义简介

一、什么是分贝 辅助单元Bel表示任何给定部件、电路或系统的输入和输出之间的对数比L,并且可以用电压、电流或功率来表示: 如果使用场量(电压或电流)代替功率量,则: 我们可以将增益或损耗因子相加为正或负dB值,而不是将其乘以比率。 分贝与功率转化的速读表如下所示:…

流程图设计制作都有哪些好用的工具

流程图是一种直观的图形表示方式&#xff0c;通常用于显示事物的过程、步骤和关系。在现代工作中&#xff0c;设计师经常需要绘制各种流程图来解释工作过程、产品设计等。本文将为您推荐7个流程图软件&#xff0c;以帮助您快速绘制高效的流程图&#xff0c;并提高工作效率。 即…

IDEA 2021.2.2设置自动热部署

1.导入包坐标 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency> 2.pom.xml添加piugins插…

Redis-双写一致性

双写一致性 双写一致性解决方案延迟双删&#xff08;有脏数据的风险&#xff09;分布式锁&#xff08;强一致性&#xff0c;性能比较低&#xff09;异步通知&#xff08;保证数据的最终一致性&#xff0c;高并发情况下会出现短暂的不一致情况&#xff09; 双写一致性 当修改了数…

光伏并网逆变器低电压穿越技术研究(Simulink仿真)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

JavaScript系列从入门到精通系列第十八篇:JavaScript中的函数作用域

文章目录 前言 一&#xff1a;函数作用域 前言 我们刚才提到了&#xff0c;在<Script>标签当中进行定义的变量、对象、函数对象都属于全局作用域&#xff0c;全局作用域在页面打开的时候生效在页面关闭的时候失效。 一&#xff1a;函数作用域 调用函数时创建函数作用域…

New Journal of Physics:不同机器学习力场特征的准确性测试

文章信息 作者&#xff1a;Ting Han1, Jie Li1, Liping Liu2, Fengyu Li1, * and Lin-Wang Wang2, * 通信单位&#xff1a;内蒙古大学物理科学与技术学院、中国科学院半导体研究所 DOI&#xff1a;10.1088/1367-2630/acf2bb 研究背景 近年来&#xff0c;基于DFT数据的机器学…

Docker安装及基本使用

一、Docker安装 1.下载关于Docker的依赖环境 在Xterm中输入以下代码安装依赖环境 回车 yum -y install yum-utils device-mapper-persistent-datalvm2 2.设置一下下载Docker的镜像源 依赖环境下载完毕以后&#xff0c;设置下载的镜像源&#xff0c;如果不设置&#xff0c…

使用Docker部署Redis(单机部署)

目录 一、查看Redis镜像版本二、拉取自己需要的镜像版本三、创建挂载目录四、添加配置文件五、运行Redis容器六、连接测试 一、查看Redis镜像版本 先去Docker Hub查看Redis镜像有那些版本&#xff0c;我部署的时候Redis最新已经到7.x的版本了&#xff0c;我这里准备部署6.x的版…

17哈希表-简单遍历

目录 LeetCode之路——383. 赎金信 分析&#xff1a; 解法一&#xff1a;哈希表 解法二&#xff1a;数组 LeetCode之路——383. 赎金信 给你两个字符串&#xff1a;ransomNote 和 magazine &#xff0c;判断 ransomNote 能不能由 magazine 里面的字符构成。 如果可以&…

pinduoduo.item_get拼多多平台根据ID取商品详情 API 封装数据接口返回值说明

参数说明 通用参数说明 version:API版本key:调用key,测试key:test_api_keyapi_name:API类型[item_search,item_get]cache:[yes,no]默认yes&#xff0c;将调用缓存的数据&#xff0c;速度比较快result_type:[json,xml,serialize,var_export]返回数据格式&#xff0c;默认为jsonl…

【SQL】MySQL中的约束

1. 主键约束&#xff08;primary key&#xff09;&#xff1a; 相当于唯一约束非空约束分为单列主键&#xff0c;多列联合主键&#xff0c;一个表只有一个主键多列联合主键的每列都不能为空 2. 自增长约束&#xff08;auto_increment&#xff09;&#xff1a; 用在单列主键后…