简介
限定仅在表尾进行插入和删除操作的线性表。允许插入和删除的一端成为栈顶,另一端成为栈低,不含任何元素的栈成为空栈,栈又称为先进先出的线性表,简称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