数据结构九——栈

文章出处:极客时间《数据结构和算法之美》-作者:王争。该系列文章是本人的学习笔记。

1栈的定义

1.1 栈的定义

栈:后进者先出,先进着后出。就像一碟盘子,如果拿走一个盘子,拿走的一定是最后放上去的那个。栈是一种操作受限的线性表,只允许在一端插入和删除。本质上来说就是一个操作不方便的数组或者链表。

栈存在的意义是什么?数据结构本身就是对特定场景的抽象,要求操作不多不少,正好满足场景需求。过多的接口会带来操作不可控,进而操作结果不可预计。

1.2 实现栈

我们可以使用数组实现一个栈,称为顺序栈;也可以用链表实现一个栈,称为链式栈。

/**
 * 数组实现的栈,在一端插入和删除,那就对数组的最后一个元素操作吧
 */
public class ArrayStack {
    //存储数据的值
    private int[] items;
    //容量
    private int n;
    //存储元素个数
    private int size;

    /**
     *
     * @param capacity
     *          容量
     */
    public ArrayStack(int capacity){
        this.n = capacity;
        items = new int[n];
    }

    /**
     * 添加元素,如果还有空间则添加成功,返回true。否则返回false。
     * @param val
     * @return
     */
    public boolean push(int val){
        if(this.size >=n) return false;
        items[size++] = val;
        return true;
    }

    /**
     * 栈内元素个数
     * @return
     */
    public int size(){
        return this.size;
    }

    /**
     * 删除栈顶元素。如果当前栈内没有元素,则抛出异常。在调用pop之前先调用size()吧。
     * @return
     */
    public int pop(){
        if(size==0) throw new IllegalArgumentException("栈目前没有任何元素,不能弹出元素");
        return items[--size];
    }
}

复杂度分析。push,pop时间复杂度为O(1),即使链表实现的栈也一样。空间复杂度式O(n)。需要大小为n的数组存储栈内元素。

1.3 支持动态扩容的栈

上面的实现中,当栈内没有空间的时候,就不能再插入元素。如果实现一个空间不受限的栈呢?这是一个数组自动扩容、自动减少空间的过程。我们只需要判断在数组满了的时候申请一个2倍容量的数组,将原有元素拷贝过去。
数据结构九——栈_第1张图片

当栈内元素只有数组容量1/4的时候,将数组容量缩小到一半。当然对内存空间不敏感的应用可以不用做这一步。

	/**
     * 添加元素
     * @param val
     */
    public void push(int val){
        if(this.size >=n) {
            grow();
        }
        items[size++] = val;
    }

    private void grow() {
        int newSize = 2*size;
        int[] newItmes = new int[newSize];
        System.arraycopy(this.items,0,newItmes,0,size);
        this.n = newItmes.length;
        this.items = newItmes;
    }

接下来我们分析一下支持动态扩容的栈入栈时间复杂度是多少。入栈操作的平均时间复杂度用摊还分析法。为了分析方便,我们先做以下假设:
1 只有扩容操作,扩容为原来数组2倍大小;
2 只有push,没有pop操作;
3 定义不涉及内存搬移的入栈操作为simple-push。

如果当前数组大小为K,当再有新的数据入栈的时候,需要申请一个大小为2K的数组,并且有K次数据迁移的操作。但是接下来的K-1次入栈操作,就无需申请内存和迁移数据。
数据结构九——栈_第2张图片

如图所示,第K次入栈,需要的K次迁移操作,可以均摊到到未来K-1次入栈操作。所以这K次操作,平均下来每次是一次数据迁移和一个simple-push。时间复杂度O(1)。

2栈的应用

2.1 栈在函数调用中的应用

2.2 栈在表达式求值中的应用

2.3 栈在括号匹配中的应用

3 浏览器支持前进后退操作

浏览器前进、后退操作是这样的。当你访问页面 a->b->c之后,你可以按后退按钮回到页面b,再继续按前进按钮到页面c。

我们使用两个栈X、Y来完成浏览器的前进后退操作。当访问页面的时候,我们按照顺序将页面入栈X。当按后退按钮的时候,从X栈取出元素,入栈Y。当按前进按钮的时候,从Y栈取出元素,入栈X。当X栈为空的时候,后退按钮不能用。当Y栈为空的时候,前进按钮不能用。

例如初始:
X
Y

访问页面a:
X:a
Y

访问页面b:
X:a,b
Y
访问页面c:
X:a,b,c
Y

按后退按钮,展示页面b:
X:a,b
Y:c

按后退按钮,展示页面a:
X:a
Y:c,b

按前进按钮,从页面a到页面b:
X:a,b
Y:c

这个时候,你访问了新页面d,这时候前进按钮应该不能用,因为d的下一个页面还没有产生;按后退按钮的话应该回到b;所以c页面不能通过前进后退转到,需要清空Y栈:
X:a,b,d
Y:

4 思考

Java虚拟机中有堆栈的概念。栈内用来存储临时变量和方法调用,堆内存储Java对象。那么Java虚拟机中的栈和这里的栈,概念一样吗?
答:不一样。数据结构中的栈是对场景的抽象,是抽象的数据机构。
JVM使用的内存分为代码区、静态数据区和动态数据区。
代码区:存放方法的二进制代码,控制代码区代码执行切换。
静态数据区:全局变量、静态变量,常量(包含final修饰的和String)。
动态数据区:分为堆区和栈区。堆区存放对象,该对象的引用存放在栈区。栈区存放运行方法的形参、局部变量和返回值。

你可能感兴趣的:(极客-算法,栈,浏览器前进后退)