一、概述
- 作用:栈可以理解为给数据临时休息的地方,数据可以进入栈,也可以从栈中出去。
- 特点:栈是一种基于先进后出(FILO)的数据结构,是一种只能在一端进行插入删除操作的特殊线性表。数据进入栈成为“压栈”或“入栈”,先进入的数据被压入栈底,最后进入的数据在栈顶,如果要获取数据,只能从栈顶开始弹出数据,也就是栈顶的数据先被读取,战帝的数据最后被读取。
既然是特殊的线性表,也就是既可以用顺序存储结构实现,也可以用链式存储结构实现。下面选择用链式存储结构来实现栈结构。
二、栈的实现


- 结构:为了保证元素“先进后出”的特性,也就是元素的出栈入栈操作都在栈顶进行,头结点head作为辅助结点,始终指向栈顶元素,元素的插入删除都是在head后面的第一个元素结点位置完成。注意和传统的单向链表结构区分开。
- 特点:先进后出。
- 代码实现:
package com.ex.stack; import java.util.Iterator; /** * 用链式结构实现栈 */ public class Stack<T> implements Iterable<T>{ //首结点 private Node<T> head; //元素个数 private int N; private class Node<T> { //数据域 private T item; //指针域 private Node<T> next; public Node(T item, Node next) { this.item = item; this.next = next; } } //构造函数 public Stack() { this.head=new Node<T>(null,null); this.N=0; } //判断栈是否为空,是则返回true,否则返回false public boolean isEmpty(){ return N==0; } //获取栈中元素的数量 public int size(){ return N; } //元素入栈 public void push(T t){ //创建新结点 //令新结点指向原第一个元素结点 Node<T> newNode=new Node(t,head.next); //令首结点指向新结点 head.next=newNode; //元素数+1 N++; } //元素出栈 public T pop(){ //获取第一个元素结点 Node<T> curr=head.next; //令首结点指向第二个元素结点 if (curr!=null){ head.next=curr.next; } //结点数-1 N--; //返回弹出的元素 return curr.item; } //以下方法和内部类均为了可以实现外部foreach遍历 @Override public Iterator<T> iterator() { return new SIterator(); } private class SIterator implements Iterator<T> { private Node<T> n; public SIterator() { this.n = head; } @Override public boolean hasNext() { return n.next!=null; } @Override public T next() { n=n.next; return n.item; } } }
View Codepackage com.ex.stack; public class StackTest { public static void main(String[] args) { Stack<Integer> stack = new Stack(); //测试入栈 stack.push(1); stack.push(2); stack.push(3); stack.push(4); stack.push(5); for (Integer val:stack) { System.out.println(val); } System.out.println("--------"); //测试出栈 System.out.println(stack.pop()); //测试isEmpty()和size() System.out.println("isEmpty:"+stack.isEmpty()); System.out.println("size:"+stack.size()); } }
三、栈的扩展
1.括号匹配问题
-
- 题目:给定一个字符串,里边可能包含"()"小括号和其他字符,请编写程序检查该字符串的中的小括号是否成对出现。例如:"(上海)(长安)":正确匹配,"上海(长安))":错误匹配。
- 思路:如果是一对括号想要正确匹配,必须先使用左括号才能使用右括号。因此可以使用栈,将待匹配的左括号入栈,遇到右括号就出栈一个左括号使其配对,当检索完字符串后栈已空则说明没有剩余的左括号则配对成功,否则失败。如果在检索过程中,发现左括号不够匹配,自然也是失败。
- 代码实现:
package com.ex.stack; /** * 用栈解决括号匹配问题 */ public class BracketMatchingTest { public static void main(String[] args) { String str="((上海)))长安"; boolean match = bracketMatch(str); System.out.println("是否正确配对:"+match); } private static boolean bracketMatch(String str) { //1.将字符串转换为数组 char[] array = str.toCharArray(); //2.依次检索字符,如果是左括号,就入栈,如果是右括号,就出栈一个左括号 Stack<Character> stack=new Stack<>(); for (int i = 0; i < array.length; i++) { if (array[i]=='('){ stack.push('('); }else if(array[i]==')'){ if (!stack.isEmpty()){ stack.pop(); }else { //左括号不够匹配 return false; } } } //3.判断栈是否已空,如果已经空则说明括号配对正确,否则失败 if (stack.isEmpty()){ return true; } //右括号不够匹配,还有剩余的左括号 return false; } }
2.逆波兰表达式
-
- 题目:
- 中缀表达式:就是我们平常生活中使用的表达式,例如:1+3*2,2-(1+3)等,它的特点是:二元运算符总是置于两个操作数中间。中缀表达式对于人们来说简单易懂可读性强,但对于计算机来说却很麻烦,不仅运算顺序不具有规律性。不同的运算符具有不同的优先级,如果计算机执行中缀表达式,需要解析表达式语义,做大量的优先级相关操作。
- 逆波兰表达式(后缀表达式):因此以一位波兰逻辑学家提出了逆波兰表达式,它的特点是运算符总是放在跟它相关的操作数之后。例如:中缀表达式 a+(b-c)*d 的逆波兰表达为 abc-d*+。
- 题目内容:给定一个只包含加减乘除四种运算的逆波兰表达式的字符串数组,求出该逆波兰表达式的结果。
- 中缀表达式:就是我们平常生活中使用的表达式,例如:1+3*2,2-(1+3)等,它的特点是:二元运算符总是置于两个操作数中间。中缀表达式对于人们来说简单易懂可读性强,但对于计算机来说却很麻烦,不仅运算顺序不具有规律性。不同的运算符具有不同的优先级,如果计算机执行中缀表达式,需要解析表达式语义,做大量的优先级相关操作。
- 题目:
-
- 思路:顺序检索字符串数组,如果当前字符串是操作数就入栈,如果是操作符就出栈两个操作数进行计算并将计算结果入栈(注意先出栈的是放在表达式的第二个位置,ab/ ——> a/b),直到检索完字符串数组做完所有计算操作,栈中会留下唯一元素即为结果。
- 代码实现:
package com.ex.stack; import java.util.regex.Pattern; /** * 用栈实现根据逆波兰表达式进行四则运算 */ public class ReversePolishNotationTest { public static void main(String[] args) { String str[]={"10","3","4","-","2","*","+"}; int calculate = calculate(str); System.out.println("计算结果为:"+calculate); } /** * @param str 逆波兰表达式字符串 * @return 解析逆波兰表达式为计算式,返回计算式的结果 */ private static int calculate(String str[]) { //1.遍历字符数组,如果当前字符串为操作数则入栈,如果当前字符串为运算符号则出栈两个操作数进行计算,并将结果入栈 Stack<Integer> stack=new Stack<>(); for (int i = 0; i < str.length; i++) { //判断当前字符串是不是数字 Pattern pattern = Pattern.compile("^-?\\d+(\\.\\d+)?$"); boolean isNumber = pattern.matcher(str[i]).matches(); if (isNumber){ //如果是数字:入栈 stack.push(Integer.valueOf(str[i])); }else { //如果是运算符号:出栈两个操作数进行计算,并将结果入栈 //result:计算结果,bottom:后出栈的内容,更栈底的操作数,top:先出栈的内容,靠上 int result=0; int top=stack.pop(); int bottom=stack.pop(); switch (str[i]){ case "+": result=bottom + top; break; case "-": result=bottom - top; break; case "*": result=bottom * top; break; case "/": result=bottom / top; break; } stack.push(result); } } //2.返回计算结果 return stack.pop(); } }
四、官方API中的栈
以JDK1.8为参考:
栈Stack<T>的底层采用Vector<T>,即线程安全的动态数组,因此Stack也是线程安全的,并且入栈出栈操作相应调用的是Vector的添加删除元素方法。
浙公网安备 33010602011771号