特点:先进后出,只涉及在一端插入和删除数据。

栈的实现方式

栈既可以用数组来实现,也可以用链表来实现。用数组实现的栈,我们叫作顺序栈,用链表实现的栈,我们叫作链式栈。
用数组实现一个固定大小的栈:

  1. // 基于数组实现的顺序栈
  2. public class ArrayStack {
  3. private String[] items; // 数组
  4. private int count; // 栈中元素个数
  5. private int n; //栈的大小
  6. // 初始化数组,申请一个大小为n的数组空间
  7. public ArrayStack(int n) {
  8. this.items = new String[n];
  9. this.n = n;
  10. this.count = 0;
  11. }
  12. // 入栈操作
  13. public boolean push(String item) {
  14. // 数组空间不够了,直接返回false,入栈失败。
  15. if (count == n) return false;
  16. // 将item放到下标为count的位置,并且count加一
  17. items[count] = item;
  18. ++count;
  19. return true;
  20. }
  21. // 出栈操作
  22. public String pop() {
  23. // 栈为空,则直接返回null
  24. if (count == 0) return null;
  25. // 返回下标为count-1的数组元素,并且栈中元素个数count减一
  26. String tmp = items[count-1];
  27. --count;
  28. return tmp;
  29. }
  30. }

管是顺序栈还是链式栈,我们存储数据只需要一个大小为 n 的数组就够了。在入栈和出栈过程中,只需要一两个临时变量存储空间,所以空间复杂度是 O(1)。
不管是顺序栈还是链式栈,入栈、出栈只涉及栈顶个别数据的操作,所以时间复杂度都是 O(1)。

栈的应用场景

1.栈在函数调用中的应用
操作系统给每个线程分配了一块独立的内存空间,这块内存被组织成“栈”这种结构, 用来存储函数调用时的临时变量。每进入一个函数,就会将临时变量作为一个栈帧入栈,当被调用函数执行完成,返回之后,将这个函数对应的栈帧出栈。
2.栈在表达式求值中的应用
编译器就是通过两个栈来实现的。其中一个保存操作数的栈,另一个是保存运算符的栈。我们从左向右遍历表达式,当遇到数字,我们就直接压入操作数栈;当遇到运算符,就与运算符栈的栈顶元素进行比较。
3.栈在括号匹配中的应用

问题:
我们假设表达式中只包含三种括号,圆括号 ()、方括号[]和花括号{},并且它们可以任意嵌套。比如,{[] ()[{}]}或[{()}([])]等都为合法格式,而{[}()]或[({)]为不合法的格式。那我现在给你一个包含三种括号的表达式字符串,如何检查它是否合法呢?
思路:
我们用栈来保存未匹配的左括号,从左到右依次扫描字符串。当扫描到左括号时,则将其压入栈中;当扫描到右括号时,从栈顶取出一个左括号。如果能够匹配,比如“(”跟“)”匹配,“[”跟“]”匹配,“{”跟“}”匹配,则继续扫描剩下的字符串。如果扫描的过程中,遇到不能配对的右括号,或者栈中没有数据,则说明为非法格式。当所有的括号都扫描完成之后,如果栈为空,则说明字符串为合法格式;否则,说明有未匹配的左括号,为非法格式。
代码实现:

  1. class Solution {
  2. public boolean isValid(String s) {
  3. Stack<Character>stack = new Stack<Character>();
  4. for(char c: s.toCharArray()){
  5. if(c=='(')stack.push(')');
  6. else if(c=='[')stack.push(']');
  7. else if(c=='{')stack.push('}');
  8. else if(stack.isEmpty()||c!=stack.pop())return false;
  9. }
  10. return stack.isEmpty();
  11. }
  12. }

代码联系:

leetcode上关于栈的题目大家可以先做20,155,232,844,224,682,496.