这里介绍两个基本数据类型实现的栈:数组与链表
1.首先我们介绍基于数组来实现栈的过程(直接看代码即可,里面都有详尽的注释)
为了秉承抽象特性,这里将接口操作与接口的实现方法进行分离
a.接口StackADT
package xidian.sl.stack; public interface StackADT<T> { /*往栈顶添加一个元素*/ public void push(T element); /*从栈顶移出一个元素,并返回该元素*/ public T pop() throws EmptyCollectionException; /*返回但不移除元素*/ public T top() throws EmptyCollectionException; /*如果栈中没有元素,返回真*/ public Boolean isEmpty(); /*返回栈中元素的数量*/ public int size(); /*返回栈的一个字符串表示*/ public String toString(); }
b.实现类ArrayStack
package xidian.sl.stack; /** * 利用数组的基本数据结构设计了一种栈的实现 * */ public class ArrayStack<T> implements StackADT<T>{ /*表示数组的默认容量*/ private final int DEFAULT_CAPACITY = 100; /*表示数组元素的数目以及下一个可用位置,相当于索引*/ private int top; /*表示栈的泛型元素数组*/ private T[] stack; /*默认构造方法*/ @SuppressWarnings("unchecked") public ArrayStack(){ top = 0; //基于数组的栈实现总是使栈底位于数组的索引0处 /** * 由泛型的知识可知,不能实例化一个泛型对象, * 因此这里先实例化了一个object数组,然后把它转化为一个泛型数组, * 这个会产生一个未显示类型转换的编译警告 * */ stack = (T[])(new Object[DEFAULT_CAPACITY]); } /*指定容量创建一个空栈,参数initialCapacity表示指定容量*/ @SuppressWarnings("unchecked") public ArrayStack(int initialCapacity){ top = 0; stack = (T[])(new Object[initialCapacity]); } /** * 添加操作分为三步 * 1.确保该数组不是满的 * 2.把数组的top引用设置为要加入到栈中的对象 * 3.增加top的值 * */ public void push(T element){ if(size() == stack.length){ expandCapacity(); //扩充其容量 } stack[top] = element; top++; } /*扩容是将数组的大小进行加倍*/ @SuppressWarnings("unchecked") private void expandCapacity(){ T[] larger = (T[])(new Object[stack.length*2]); for(int index = 0; index< stack.length; index++){ larger[index] = stack[index]; //把原数组的内容复制到新数组中 } stack = larger; } /** * 移除操作 * 1.确保栈不为空 * 2.减少top计数器 * 3.设置一个临时引用等于stack[top]的元素 * 4.设置stack[top]为空 * 5.返回该临时引用 * */ public T pop() throws EmptyCollectionException{ if(isEmpty()){ throw new EmptyCollectionException("stack"); } top--; T result = stack[top]; stack[top] = null; return result; } /*返回栈顶元素*/ public T top() throws EmptyCollectionException{ if(isEmpty()){ throw new EmptyCollectionException("stack"); } return stack[top-1]; } /*判断是否为空*/ public Boolean isEmpty() { Boolean bool = false; if(top == 0){ bool = true; } return bool; } /*获得当前容量*/ public int size() { return top; } }
c.异常处理类EmptyCollectionException
package xidian.sl.stack; @SuppressWarnings("serial") public class EmptyCollectionException extends Exception{ public EmptyCollectionException(){ super(); } public EmptyCollectionException(String mess){ super(mess); } }
2.基于链表实现的
package xidian.sl.stack; public class LindedStack<T> implements StackADT<T>{ //在栈中存储的元素数量 private int count; //指向栈顶的指针 private LinearNode<T> top; public LindedStack(){ count = 0; top = null; } @Override public Boolean isEmpty() { Boolean bool = false; if(count == 0){ bool = true; } return bool; } @Override /** * 1.确保栈不为空 * 2.设置一个临时引用等于栈顶元素 * 3.设置top引用等于栈顶节点的next引用 * 4.递减栈的元素引用 * */ public T pop() throws EmptyCollectionException { if(isEmpty()){ throw new EmptyCollectionException("Stack"); } T result = top.getElement(); top = top.getNext(); count--; return result; } @Override /** * 1.创建新节点,该节点含有一个引用,指向要放置到栈中的对象 * 2.把新节点的next引用设置为指向当前栈顶(如果栈为空,那它就是null) * 3.把top引用设置为指向该新节点 * 4.递增栈的元素计数 * */ public void push(T element) { LinearNode<T> temp = new LinearNode<T>(element); temp.setNext(top); top = temp; count++; } @Override public int size() { return count; } @Override public T top() throws EmptyCollectionException { return top.getElement(); } }
上面实现中设计到的一个节点类:
package xidian.sl.stack; /** * 节点类,含有另个引用,一个指向链表的下一个LinearNode<T>节点, * 另一个指定本节点中存储的元素 * */ public class LinearNode<T> { /*指向下一个节点*/ private LinearNode<T> next; /*本节点存储的元素*/ private T element; /*创建一个空的节点*/ public LinearNode(){ next = null; element = null; } /*创建一个存储了特殊元素的节点*/ public LinearNode(T elem){ next = null; element = elem; } /*返回下一个节点*/ public LinearNode<T> getNext(){ return next; } /*设置下一个节点*/ public void setNext(LinearNode<T> node){ next = node; } /*获得当前节点存储的元素*/ public T getElement() { return element; } /*设置当前节点存储的元素*/ public void setElement(T element) { this.element = element; } }
以上代码就是两种实现栈的全过程,可能大家更关心的是栈能解决的实际问题,下面我将列出一些我所知道的用栈最为合适的应用:
由于栈的特性是:1.后进先出(last in,first out) 2.所有的操作都是在一端进行操作的,因此如果需要访问集合中间或者底部的元素,就不适合使用栈作为数据结构了
1.字处理器的取消操作通常是用栈来实现的:当我们对某个文档进行修改(一些列的操作)时,字处理器通过把每个操作压入栈中来跟踪这些操作,如果执行取消操作,字处理器
就会从栈中弹出最近执行的操作并返回上一操作(这就很好的利用了栈LIFO特性)。只是字处理器栈的容量是有一定限制,当达到容量时并不会进行扩容而是将栈底的元素依次
进行丢弃。
2.使用栈计算后缀表达式:我们一般平时遇到的表达式都是中缀表达式,即操作符位于操作数之间的,如8 + 9。而后缀表达式中操作符是位于操作数之后的,如8 9 +。后缀表达式优于中缀表达式的原因:更容易计算,之需要从左往右进行扫描,而不需要考虑优先规则和括号等。因此程序设计语言的编译器和运行时的环境往往都是使用后缀表达式的。
后缀表达式的执行方式:从左到右进行扫描,把每个操作符应用到其之前的两个相邻的操作数,并用该计算结果代替该操作符,最后就剩下终值。
如:(3 * 4 - (2 + 5)) * 4 / 2 =========>3 4 * 2 5 + - 4 * 2 /
实现代码如下:
package xidian.sl.stack; import java.util.StringTokenizer; public class PostfixEvaluator { //加法符合的常量 private final char ADD = '+'; //减法符合的常量 private final char SUB = '-'; //乘法符合的常量 private final char MUL = '*'; //除法符合的常量 private final char DIV = '/'; //保存运算的栈 private ArrayStack<Integer> stack; //栈是被设计为存储对象的 public PostfixEvaluator(){ stack = new ArrayStack<Integer>(); } //计算表达式 public int evaluate(String expr) throws EmptyCollectionException{ int op1, op2, result = 0; String token; //为指定字符串构造一个 string tokenizer。tokenizer 使用默认的分隔符集 " \t\n\r\f",即:空白字符、制表符、换行符、回车符和换页符。分隔符字符本身不作为标记。 StringTokenizer stringTokenizer = new StringTokenizer(expr); while(stringTokenizer.hasMoreTokens()){ token = stringTokenizer.nextToken(); if(isOperator(token)){ //这里要注意先出来的元素应该是第二个操作数,后出来的是第一个操作数 op2 = (stack.pop()).intValue(); op1 = (stack.pop()).intValue(); result = evalSingleOp(token.charAt(0), op1, op2); stack.push(Integer.valueOf(result)); }else{ stack.push(Integer.valueOf(token)); } } return result; } //判断该元素是否为操作符 private boolean isOperator(String token){ return (token.equals("+")|| token.equals("-")|| token.equals("*")|| token.equals("/")); } //计算 private int evalSingleOp(char opera, int op1, int op2){ int result = 0; switch (opera) { case ADD: result = op1 + op2; break; case SUB: result = op1 - op2; break; case MUL: result = op1 * op2; break; case DIV: result = op1 / op2; break; default: break; } return result; } }
测试类main:
package xidian.sl.stack; import java.util.Scanner; public class Postfix { public static void main(String[] args){ String expression, again; int result; try { Scanner in = new Scanner(System.in); do{ PostfixEvaluator postfixEvaluator = new PostfixEvaluator(); expression = in.nextLine(); result = postfixEvaluator.evaluate(expression); System.out.println(); System.out.println("运算结果为 = "+result); System.out.println("还要计算其他表达式[y/n]"); again = in.nextLine(); System.out.println(); } while(again.equalsIgnoreCase("y")); } catch (Exception e) { System.out.println("输入的表达式有错"); e.printStackTrace(); } } }
运行结果如下:
3.