数据结构之栈

202 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第 24 天,点击查看活动详情

日积月累,水滴石穿 😄

栈的定义

栈(Stack)是线性结构的一种,栈中的元素拥有先进后出的特点,即 Last In First Out (LIFO)。比如:放盘子的时候都是从下往上一个个的放,拿的时候是从上往下一个个的拿,这种其实就是栈型数据结构。

栈的原理

栈是一个限定仅在表尾进行插入和删除操作的线性表,这一端被称为栈顶,相对的,把另一端称为栈底。向一个栈插入新元素称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。

image.png

栈的实现方式

由于栈的结构也是线性表,那么栈的实现可以使用链表或者数组进行实现。各位可能会有疑问了,既然数组和链表可以实现,为什么要单独将栈拎出来呢作为数据结构呢?这是因为数组和链表暴露了太多的接口,在不熟悉其中方法的情况下,在使用上可能会出错,所以在某些特定场景下最好是选择栈这个数据结构。

  • 基于数组实现栈:数组头为栈底,入栈和出栈在数组尾部进行插入和删除操作,操作数组尾部时间复杂度为O(1)。
  • 基于单链表实现栈:链表头节点为栈底,入栈和出栈在链表的头部进行插入和删除操作,操作链表头部时间复杂度为O(1)。每次入栈时将 next 指针指向新节点,出栈时将 next 指向下个节点。

Java 中并没有栈结构对应的的接口,但是有 Stack 类,但是 Stack类已经过时了,并且是基于 Vector 实现的。由于 Vector 的相关方法都是加锁实现的,所以更推荐使用 Deque(双端队列)来完成栈的相关操作。

数组栈

/**
 * @author: cxyxj
 */
public class ArrayStack<T> {

    /**
     * 数据
     */
    Object[] data;

    /**
     * 元素个数
     */
    int size;

    /**
     * 初始化数组容量
     */
    public ArrayStack(int cap) {
        data = new Object[cap];
    }

    /**
     * 入栈
     */
    public void push(T e) {
        //这里可以做数组扩容、缩减
        
        data[size++] = e;
    }

    /**
     * 出栈
     */
    public T pop() {
        if (isEmpty()){
            return null;
        }
        Object datum = data[--size];
        //方便 GC
        data[size] = null;
        return (T)datum;
    }

    /**
     * 判断栈中是否还有元素
     */
    public boolean isEmpty() {
        return size() == 0;
    }
    /**
     * 返回栈中元素个数
     */
    public synchronized int size() {
        return size;
    }

    public void print() {
        System.out.print("size:" + size);
        for(int i = 0; i < size; i++) {
            System.out.print(data[i] + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        // 先进后出
        ArrayStack mystack = new ArrayStack(2);
        mystack.push("1110");
        mystack.push("2222");
        mystack.print();
        System.out.println(mystack.pop());
        System.out.println(mystack.pop());
        mystack.print();
        System.out.println(mystack.pop());
    }
}
输出结果
size:21110 2222 
2222
1110
size:0
null

链表栈

public class LinkedListStack {

    private MyNode headNode;

    private int size;

    public void push(String data){
        // 构建新节点
        MyNode newNode = new MyNode(data);
        // 将旧的头节点 作为 新节点的下个节点
        newNode.next = headNode;
        // 新节点作为头节点
        headNode = newNode;
        size++;
    }

    public String pop(){
        // 头节点为 null,说明链表中没有元素了
        if(headNode == null){
            return null;
        }
        // 获得头节点的值
        String item = headNode.item;
        //获得头节点的下个节点
        MyNode headNext = headNode.next;
        //将下个节点赋值给头节点
        headNode = headNext;
        size--;
        return item;
    }

    /**
     * 判断栈中是否还有元素
     */
    public boolean isEmpty() {
        return size() == 0;
    }
    /**
     * 返回栈中元素个数
     */
    public synchronized int size() {
        return size;
    }

    public void print(){
        MyNode cur = headNode;
        while (cur != null){
            System.out.println(cur.item + " ");
            cur = cur.next;
        }
    }

    public static void main(String[] args) {
        LinkedListStack linkedListStack = new LinkedListStack();
        linkedListStack.push("111");
        linkedListStack.push("2222");
        linkedListStack.push("3333");
        System.out.println( linkedListStack.pop());
        System.out.println( linkedListStack.pop());
        System.out.println( linkedListStack.pop());
        System.out.println( linkedListStack.pop());
        linkedListStack.print();
    }

    private static class MyNode{

        String item;

        MyNode next;

        public MyNode(String item){
            this.item = item;
        }

    }
}
结果如下
3333
2222
111
null

看完了上面数组栈链表栈的实现源码,如果各位小伙伴看了小杰前面数组、链表的俩篇文章,小伙伴是不是心里大呼,就这就这,太简单了吧,数组只允许操作尾元素,链表只允许操作头节点。

栈的应用

括号匹配

假如传入的表达式中包含三种括号:圆括号、方括号和花括号,并且可以任意嵌套。例如{[()][{}]}[{([])}]为正确格式,而{[]()][(])]为不正确的格式。那怎么检测传入的表达式是否正确呢?

思路

获得传入的字符,循环判断每个字符.

  • 如果是左括号,则直接入栈。

  • 如果是右括号,则直接弹出栈顶元素。

    • 1、判断栈是否为空,为空则代表没有对应的左括号与之匹配,返回false。
    • 2、不为空,判断栈顶元素是不是相匹配的同类右括号,如果不是则直接返回false,
    • 3、当字符读完时,判断栈是否是空的,如果是则表达式是合法的。反之,则是异常表达式。
public class KuoHaoStack {

    public static boolean isOk(String s) {

        ArrayStack<Character> brackets = new ArrayStack<>(10);
        char c[] = s.toCharArray();
        Character top;
        for (char x : c) {
            switch (x) {
                case '}':
                    top = brackets.pop();
                    if (top == null || top != '{') {
                        return false;
                    } else {
                        break;
                    }
                case ')':
                    top = brackets.pop();
                    if (top == null || top != '(') {
                        return false;
                    } else {
                        break;
                    }
                case ']':
                    top = brackets.pop();
                    if (top == null || top != '[') {
                        return false;
                    } else {
                        break;
                    }
                default:
                    brackets.push(x);
                    break;
            }
        }
        return brackets.isEmpty();
    }

    public static void main(String[] args) {
        String s = "[()[]]";
        System.out.println(s + "匹配结果:" + isOk(s));
        String s1 = "[()[]}";
        System.out.println(s1 + "匹配结果:" + isOk(s1));
    }

}
输出结果
[()[]]匹配结果:true
[()[]}匹配结果:false

  • 如你对本文有疑问或本文有错误之处,欢迎评论留言指出。如觉得本文对你有所帮助,欢迎点赞和关注。