简介
限定仅在表尾进行插入和删除操作的线性表。允许插入和删除的一端成为栈顶,另一端成为栈低,不含任何元素的栈成为空栈,栈又称为先进先出的线性表,简称LIFO结构。
栈的插入操作,叫做进栈,也称压栈,入栈。
栈的删除操作,也叫出战,也有的叫做弹栈。
栈的附加功能
- Peep 窥视:返回堆栈的栈顶元素(不删除)
- isEmpty:检查堆栈是否为空。
- isFull:检查堆栈是否已经满了。
栈的存储表示方法:
-
顺序栈:利用顺序存储结构实现的栈(①利用一组地址连续的存储单元一次存放从栈底到栈顶的数据元素;②附设指针top指向栈顶元素的位置,base指针指向栈底元素位置;③采用动态分配原则)
- 初始化:为顺序栈分配一个数组空间(stacksize,栈的最大容量),base和top同时指向栈底,表示空栈。
- 入栈:判断栈是否已满,满则报错,否则将元素压入栈顶,top加一。
- 出栈:判断栈是否为空,空则报错,否则将top减一,栈元素出栈。
-
链栈:采用链式存储结构实现的栈,通常用单链表表示(无头节点)。
- 初始化:构造一个空栈,无头结点,将栈顶指针置为空。
- 入栈:为新的栈顶元素分配结点空间,将新结点插入到栈顶,修改栈顶指针。
- 出栈:判断栈是否为空,空则报错,否则取栈顶元素,修改栈顶指针,释放原栈顶元素空间。
顺序栈与链栈的优缺点:
- 顺序栈受到最大空间容量的限制,在满员时扩容工作量较大,在无法估计栈可能达到的最大容量时应使用链栈。
栈的应用:
- 表达式评估。
- 递归编程中实现函数调用。
入栈
出栈
实现一个栈
栈主要包含两个操作,入栈和出栈,也就是在栈顶插入一个数据和从栈顶删除一个数据。栈既可以用数组来实现,也可以用链表来实现。用数组实现的栈,我们叫作顺序栈,用链表实现的栈,我们叫作链式栈。 根据上一节我们实现的数组代码我们来实现一个自己的栈。
栈的抽象接口
public interface Stack<E> { /** * 栈的容量 * @return */ int getSize(); /** * 栈是否为空 * @return */ boolean isEmpty(); /** * 向栈中添加元素 * @param e */ void push(E e); /** * 向栈中取出元素 * @return */ E pop(); /** * 查看栈顶的元素 * @return */ E peek(); }数组栈
public class ArrayStack<E> implements Stack<E> { private E[] data; private int size; public ArrayStack(){ this(10); } public ArrayStack(int capacity){ this.data = (E[]) new Object[capacity]; this.size = 0; } @Override public int getSize() { return size; } @Override public boolean isEmpty() { return size == 0; } /** * 获取栈的容量 * @return */ public int getCapacity(){ return data.length; } @Override public void push(E e) { addLast(e); } @Override public E pop() { return removeLast(); } @Override public E peek() { return get(size - 1); } /** * 获取指定索引位置的值 * @param index * @return */ private E get(int index){ if(index < 0 || index >= size){ throw new IllegalArgumentException("Get failed. index is illegal."); } return data[index]; } /** * 数组尾部新增元素 * @param e */ private void addLast(E e){ add(size, e); } /** * 在指定位置插入元素 * @param index * @param e */ private void add(int index, E e){ if(index < 0 || index > size){ throw new IllegalArgumentException("AddLast failed. require index >=0 and index <= size"); } if(size == data.length){ //扩容 resize(2 * data.length); } for(int i = size - 1; i >= index; i --){ data[i + 1] = data[i]; } data[index] = e; size ++; } /** * 数组扩容 * @param newCapacity */ private void resize(int newCapacity){ E[] newData = (E[])new Object[newCapacity]; for (int i = 0; i < size; i++) { newData[i] = data[i]; } data = newData; } /** * 删除栈中最后一个元素 * @return */ private E removeLast(){ return remove(size - 1); } /** * 删除栈数组中index位置的元素, 并返回删除的元素 * @param index * @return */ private E remove(int index){ if(index < 0 || index >= size){ throw new IllegalArgumentException("Remove failed. index is illegal."); } E ret = data[index]; for (int i = index; i < size - 1; i++) { data[i] = data[i + 1]; } size --; data[size] = null; if(size == data.length / 4 && data.length / 2 != 0){ //当数组长度缩小为原数组的4分之一的时候才进行数组的缩容, //缩小为原数组的2分之一,预留空间,防止有数据添加导致扩容浪费性能 resize(data.length / 2); } return ret; } @Override public String toString(){ StringBuilder sb = new StringBuilder(); sb.append("Stack: "); sb.append("["); for (int i = 0; i < size; i++) { sb.append(data[i]); if(i != size - 1){ sb.append(", "); } } sb.append("] top"); return sb.toString(); } }链表栈
public class LinkedListStack<E> implements Stack<E> { private class Node<E>{ public E e; public Node<E> next; public Node(E e){ this.e = e; } @Override public String toString() { return e.toString(); } } private Node<E> dummyHead; private Integer size; public LinkedListStack(){ dummyHead = new Node<>(null); size = 0; } @Override public int getSize() { return size; } @Override public boolean isEmpty() { return size == 0; } @Override public void push(E e) { Node<E> node = new Node<>(e); node.next = dummyHead.next; dummyHead.next = node; size++; } @Override public E pop() { if(isEmpty()){ throw new IllegalArgumentException("Cannot pop from an empty stack."); } Node<E> prev = dummyHead; Node<E> retNode = prev.next; prev.next = retNode.next; retNode.next = null; size -- ; return retNode.e; } @Override public E peek() { if(isEmpty()){ throw new IllegalArgumentException("Cannot pop from an empty stack."); } return dummyHead.next.e; } @Override public String toString(){ StringBuilder sb = new StringBuilder(); sb.append("Stack: top "); Node<E> cur = dummyHead.next; while (cur != null){ sb.append(cur + "->"); cur = cur.next; } sb.append("NULL"); return sb.toString(); } }栈应用
函数应用
操作系统给每个线程分配了一块独立的内存空间,这块内存被组织成“栈”这种结构, 用来存储函数调用时的临时变量。每进入一个函数,就会将临时变量作为一个栈帧入栈,当被调用函数执行完成,返回之后,将这个函数对应的栈帧出栈。
表达式求值
我将算术表达式简化为只包含加减乘除四则运算,比如:34+13*9+44-12/3。对于这个四则运算编译器就是通过两个栈来实现的。其中一个保存操作数的栈,另一个是保存运算符的栈。我们从左向右遍历表达式,当遇到数字,我们就直接压入操作数栈;当遇到运算符,就与运算符栈的栈顶元素进行比较。
如果比运算符栈顶元素的优先级高,就将当前运算符压入栈;如果比运算符栈顶元素的优先级低或者相同,从运算符栈中取栈顶运算符,从操作数栈的栈顶取 2 个操作数,然后进行计算,再把计算 完的结果压入操作数栈,继续比较。
括号匹配
我们假设表达式中只包含三种括号,圆括号 ()、方括号 [] 和花括号{},并且它们可以任意嵌套。比如,{[{}]}或 [{()}([])] 等都为合法格式,而{[}()] 或 [({)] 为不合法的格式。在给你一个包含三种括号的表达式字符串。我们用栈来保存未匹配的左括号,从左到右依次扫描字符串。当扫描到左括号时,则将其压入栈中;当扫描到右括号时,从栈顶取出一个左括号。如果能够匹配,比 如“(”跟“)”匹配,“[”跟“]”匹配,“{”跟“}”匹配,则继续扫描剩下的字符串。如果扫描的过程中,遇到不 能配对的右括号,或者栈中没有数据,则说明为非法格式。当所有的括号都扫描完成之后,如果栈为空,则说明字符串为合法格式;否则,说明有未匹配的左括号,为非法格式。
/** * @Author: huangyibo * @Date: 2021/12/26 18:58 * @Description: * 给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。 * * 有效字符串需满足: * * 左括号必须用相同类型的右括号闭合。 * 左括号必须以正确的顺序闭合。 * 注意空字符串可被认为是有效字符串。 */ public class LeetCodeQuestion { public boolean isValid(String s) { Stack<Character> stack = new Stack<Character>(); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if(c == '(' || c == '{' || c == '{'){ stack.push(c); }else { if(stack.isEmpty()){ return false; } Character pop = stack.pop(); if(c == ')' && pop != '('){ return false; } if(c == '}' && pop != '{'){ return false; } if(c == ']' && pop != '['){ return false; } } } //如果栈里面还有字符的话,那么也不行,因为意味着还有字符没办法匹配 return stack.isEmpty(); } }参考: https://blog.csdn.net/weixin_39084521/article/details/89820132
https://www.cnblogs.com/smallzhen/p/14152557.html