数据结构与算法5-栈

目录

栈的特点

栈的面试题

如何理解栈结构

为什么要使用栈

栈的分类

如何实现栈

算法题:括号匹配

栈的应用


栈的特点

栈有一个特点,先进后出,或者说,后进先出

栈的面试题

有一个很经典的面试题,用栈可以比较容易解决,那就是判断括号是不是一对,比如

{[()]}这样就是一对,{{[)}}不是一对

这个题,可以用正则啊,可以拆分匹配等等,其实都是可以的,但是不如栈方便

再比如,做一个历史记录功能,最贴切的,就是浏览器的后退功能,也完全可以用栈实现,点击一步,那就入栈,后退,那就出栈,当然,后退这个地方也需要用一个栈记录,毕竟浏览器有前进有后退,可以来回切换

如何理解栈结构

理解栈的结构,其实也很简单,我们进行一个类比,你现在有很多书,摞起来,你放的时候肯定不是放最下面,是放最上面,你拿的时候,也是从最上面开始拿,不是最下面,这也就是上面说的站点特征,后进先出,last in first out,简称LIFO

栈,其实就是仅仅在表尾进行插入和删除操作的线性表,这一端被称为栈顶,相对的,另一边就是栈底

往一个栈里插入元素,称为进栈、压栈、入栈,它会把新元素放在栈顶元素的上面,成为新的栈顶元素

从一个栈里删除元素,称为出栈、退栈,它会把栈顶元素删除,相邻的元素成为新的栈顶元素

栈,其实就是一个特殊的数组或者链表

为什么要使用栈

既然栈是一个特殊的数组或者链表,那为什么还要用栈,栈还有这么些个限制

仔细想想,其实栈的限制也是一种保护,限制了只能操作这个范围,而不能操作其他的

栈的分类

栈分为两种,基于数组的栈和基于链表的栈

基于数组的栈,通常以数组头为栈底,数组头到数组尾的方向为栈顶的延展方向

基于单链表的栈,通常以链表头为栈顶,这样方便节点的插入和删除,压栈产生的节点一直出现在链表头

这两种实现方式最大的区别,就是扩容,基于链表,那就有链表的特性,意味着天然可以扩容,而数组不行,并且因为是从头访问,所以数组和链表的访问复杂度没区别

但是实际在使用中,我们用更多的还是数组实现的,链表的实现存在栈溢出的风险

如何实现栈

因为栈有多种实现,我们先写一个接口


/**
 * @author create by YanXi on 2022/9/3 11:00
 * @email [email protected]
 * @Description: 数组实现的栈
 */
public class AyyayStack implements MyStack{

    // 要用数组实现,一定有一个数组
    private I[] stack = (I[]) new Object[1];;
    // 元素个数
    private int itemSize = 0;

    public AyyayStack(int initSize) {
        if (0 > initSize) {
            this.stack = (I[]) new Object[initSize];
        }
    }

    /**
     * @Description: 入栈
     * @Author: create by YanXi on 2022/9/4  14:36
     * @Email: [email protected]
     *
     * @param item 插入的参数
     * @exception
     * @Return: void
    */
    @Override
    public void push(I item) {
        // 判断是否需要扩容
        if (stack.length <= itemSize) {
            resize(itemSize * 2);
        }
        stack[itemSize++] = item;
    }

    /**
     * @Description: 扩容
     * @Author: create by YanXi on 2022/9/4  14:36
     * @Email: [email protected]
     *
     * @param size
     * @exception
     * @Return: void
    */
    public void resize(int size) {
        I[] tmp = (I[]) new Object[size];
        for (int i = 0; i < itemSize; i++) {
            tmp[i] = stack[i];
        }
        stack = tmp;
    }

    /**
     * @Description: 出栈
     * @Author: create by YanXi on 2022/9/4  14:36
     * @Email: [email protected]
     *
     * @param
     * @exception
     * @Return: I
    */
    @Override
    public I pop() {
        if (isEmpty()) {
            return null;
        }
        // 注意,是 --size,不是size--,一定要先减去在用,因为入栈的时候加了
        I item = stack[--itemSize];
        stack[itemSize] = null;
        // 判断是否需要缩减容量
        if (itemSize > 0 && itemSize <= stack.length >> 1){
            resize(stack.length >> 1);
        }
        return item;
    }

    /**
     * @Description: 获取栈大小
     * @Author: create by YanXi on 2022/9/4  14:37
     * @Email: [email protected]
     *
     * @param
     * @exception
     * @Return: int
    */
    @Override
    public int size() {
        return itemSize;
    }

    /**
     * @Description: 栈是否为空
     * @Author: create by YanXi on 2022/9/4  14:37
     * @Email: [email protected]
     *
     * @param
     * @exception
     * @Return: boolean
    */
    @Override
    public boolean isEmpty() {
        return itemSize == 0;
    }
    
}

算法题:括号匹配

看一个算法题,也是我们提到的括号匹配问题,是力扣的原题哦~

给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

    左括号必须用相同类型的右括号闭合。
    左括号必须以正确的顺序闭合。
    每个右括号都有一个对应的相同类型的左括号。

我们用栈来实现一下,就用我们自己实现的栈来实现


/**
 * @author create by YanXi on 2022/9/6 6:18
 * @email [email protected]
 * @Description: 括号匹配测试
 */
public class StackTest {

    public static void main(String[] args) {
        String need = "(){}[]";
        System.out.println(String.format("isMatch(\"%s\") = %s" , need, isMatch(need)));
        String need1 = "[(){}[]";
        System.out.println(String.format("isMatch(\"%s\") = %s" , need1, isMatch(need1)));
    }

    public static boolean isMatch(String reg) {
        AyyayStack stack = new AyyayStack<>(15);
        char[] chars = reg.toCharArray();
        Character pop;
        
        for (char aChar : chars) {
switch (aChar){
                case '(':
                case '[':
                case '{':
                    stack.push(aChar);
                    break;
                case ')':
                    if (stack.isEmpty()) {
                        return false;
                    }
                    pop = stack.pop();
                    if (null == pop) {
                        return false;
                    } else if ('(' == (pop.charValue())) {
                        break;
                    } else {
                        return false;
                    }
                case ']':
                    if (stack.isEmpty()) {
                        return false;
                    }
                    pop = stack.pop();
                    if (null == pop) {
                        return false;
                    } else if ('[' == (pop.charValue())) {
                        break;
                    } else {
                        return false;
                    }
                case '}':
                    if (stack.isEmpty()) {
                        return false;
                    }
                    pop = stack.pop();
                    if (null == pop) {
                        return false;
                    } else if ('{' == (pop.charValue())) {
                        break;
                    } else {
                        return false;
                    }
                default:
                    return false;
            }
        }
        return stack.isEmpty();
    }
    
}

数据结构与算法5-栈_第1张图片

 时间复杂度为O(n)

栈的应用

想一想,哪些地方和栈类似

最常用的,是不是方法调用,比如A方法调用了B,然后再调用了C,是不是A方法是最后结束的?

在数学上,计算是不是也是这样,比如 1 + 2*2 - 4/2

所以说,也可以用栈实现一个简易的计算器

创建两个栈,一个存数字,一个存运算符

只要是数字,就入栈,如果是运算符,分为两种情况

        1、当前运算符和运算符栈顶运算符比较,如果比栈顶运算符的优先级高,那就直接入栈

        2、当前运算符和运算符栈顶运算符比较,如果比栈顶运算符的优先级低或者相同,不入栈,并且数字栈出栈两个,这两个数字按照当前运算符运算,运算完成,再将数字结果和后续符号入栈

仔细想想,是这么个道理不?代入上面写的例子

1,入数字栈

+,入符号栈

2,入数字栈

*,优先级比+高,入符号栈

2,入数字栈

-,比*优先级低,不入栈,取出数字栈两个,也就是2-2,执行,2*2=4,4入数字栈,-入符号栈

往后的类似

是不是?

我们上面还说过,用栈也可以实现浏览器的前进和后退功能,如何实现呢?

假设浏览网页A,点击B,再点击C,点击D

可以每次点击都入栈,后退的时候,入另一个栈

比如我现在在C,栈1应该是ABC,我现在点击后退,C从栈1挪到栈2,页面就到了B

当我在B,点击前进,栈2出栈,C

当然,还有一个现象,那就是在B页面,直接点击D,D返回不是C,而是B,所以,当点击了新的地方, 就应该清空栈2

说白了, 就是用两个栈,来严格维护网页的前面和后面

这就是栈大概的总结,栈是一个很神奇的结构,也是一个很好用的结构,用的好的话,能给我们一些场景简化很多,带来很多便利

你可能感兴趣的:(java日常分享,数据结构相关,面试,栈,stack,栈的应用)