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


    一、什么是栈?

    image

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

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

    二、为什么需要栈?

    1.栈是一种操作受限的数据结构,其操作特性用数组和链表均可实现。

    2.但,任何数据结构都是对特定应用场景的抽象,数组和链表虽然使用起来更加灵活,但却暴露了几乎所有的操作,难免会引发错误操作的风险。

    3.所以,当某个数据集合只涉及在某端插入和删除数据,且满足后进者先出,先进者后出的操作特性时,我们应该首选栈这种数据结构。

    三、如何实现栈?

    1.栈的API

    public class Stack<Item> {
    //压栈
    public void push(Item item){}
    //弹栈
    public Item pop(){}
    //是否为空
    public boolean isEmpty(){}
    //栈中数据的数量
    public int size(){}
    //返回栈中最近添加的元素而不删除它
    public Item peek(){}
    }
    

    2.数组实现(自动扩容)

    image

    时间复杂度分析

    根据均摊复杂度的定义,可以得数组实现(自动扩容)符合大多数情况是O(1)级别复杂度,个别情况是O(n)级别复杂度,比如自动扩容时,会进行完整数据得拷贝。

    空间复杂度分析

    在入栈和出栈的过程中,只需要一两个临时变量存储空间,所以是O(1)级别。我们说空间复杂度的时候,是指除了原本的数据存储空间外,算法运行还需要额外的存储空间。
    image

    四、栈的应用

    1.栈在函数调用中的应用

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

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

    image

    2.栈在表达式求值中的应用(比如:34 + 13 * 9 + 44 - 12 / 3)

    image
    利用两个栈,其中一个用来保存操作数,另一个用来保存运算符。我们从左向右遍历表达式,当遇到数字,我们就直接压入操作数栈;当遇到运算符,就与运算符栈的栈顶元素进行比较,若比运算符栈顶元素优先级高,就将当前运算符压入栈,若比运算符栈顶元素的优先级低或者相同,从运算符中取出栈顶运算符,从操作数栈顶取出2个操作数,然后进行计算,把计算完的结果压入操作数栈,继续比较。

    3.栈在括号匹配中的应用(比如:{}{()}

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

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

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

    五、思考

    1.我们在讲栈的应用时,讲到用函数调用栈来保存临时变量,为什么函数调用要用“栈”来保存临时变量呢?用其他数据结构不行吗?

    答:其实,我们不一定非要用栈来保存临时变量,只不过如果这个函数调用符合后进先出的特性,用栈这种数据结构来实现,是最顺理成章的选择。
    从调用函数进入被调用函数,对于数据来说,变化的是什么呢?是作用域。所以根本上,只要能保证每进入一个新的函数,都是一个新的作用域就可以。而要实现这个,用栈就非常方便。在进入被调用函数的时候,分配一段栈空间给这个函数的变量,在函数结束的时候,将栈顶复位,正好回到调用函数的作用域内。

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

    答:虽然内存中的栈和数据结构的栈不是一回事,即内存中的栈是一段虚拟的内存空间,数据结构中的栈是一种抽象的数据类型,但是它们都有“栈”的特性——后进先出,所以都叫“栈”也无可厚非。

    六、其他

    leetcode上有关栈的题目:20,155,232,844,224,682,496

  • 相关阅读:
    孤荷凌寒自学python第114天区块链028以太坊智能合约007
    孤荷凌寒自学python第113天区块链027以太坊智能合约006
    孤荷凌寒自学python第112天认识区块链026
    孤荷凌寒自学python第111天区块链025eth智能合约004
    孤荷凌寒自学python第110天区块链024ETH智能合约003
    孤荷凌寒自学python第109天区块链023初识eth智能合约002
    孤荷凌寒自学python第108天区块链022之eth智能合约001
    孤荷凌寒自学python第106天认识区块链020
    孤荷凌寒自学python第105天认识区块链019
    Upenn树库的基本框架
  • 原文地址:https://www.cnblogs.com/zyl1994/p/14851398.html
Copyright © 2020-2023  润新知