栈抽象数据类型
栈(Stack)是一种特殊的线性表,其插入和删除只允许在线性表的一端进行。允许操作的一端称为栈顶(Top),不允许操作的一端称为栈底(Bottom)。栈中插入元素的操作称为入栈(Push),删除元素的操作称为出栈(Pop)。没有元素的栈称为空栈。
由于栈的插入和删除只能在栈顶进行,每次入栈元素即成为栈顶元素,每次出栈元素总是最后一个入栈元素,因此栈的特点就是先进后出。栈的基本操作包括创建栈、判断栈是否为空、入栈、出栈和取出栈顶元素等。栈不支持对指定位置的插入、删除等操作。现在声明栈接口如下,描述栈抽象数据类型。
package stack;
public interface Stack<T> {//栈接口,描述抽象数据类型
public abstract boolean isEmpty();
public abstract void push(T x);//元素x入栈
public abstract T peek();//返回栈顶元素,未出栈
public abstract T pop();//出栈,返回栈顶元素
}
接下来我们会用顺序栈以及链式栈来实现stack栈接口。
顺序栈
采用顺序存储结构的栈称为顺序栈。声明顺序栈的泛型类如下,实现栈泛型接口,使用顺序表作为成员变量实现栈,约定null不能入栈。
package stack;
import dataStructure.SeqList;
public class SeqStack<T> implements Stack<T>{
private SeqList<T> list;
public SeqStack(int length) {
this.list=new SeqList<T>(length);
}
public SeqStack() {
this(64);
}
public boolean isEmpty() {
return this.list.isEmpty();
}
public void push(T x) {
this.list.insert(x);
}
public T peek() {
return this.list.get(list.size()-1);
}
public T pop() {
return list.remove(list.size()-1);
}
}
其中入栈和出栈实现为顺序表尾插入和顺序表尾删除,时间复杂度为O(1),顺序表的插入方法已实现自动扩充数组功能,当需要扩容的时候,入栈时间复杂度为O(n)。
注意:栈线性表是不同的抽象数据类型,上述栈泛型类只是使用已有的顺序表进行设计,也可声明数组作为成员变量实现栈,我们也可以使用Java自带的Deque来实现栈。
链式栈
采用链式存储结构的栈称其为链式栈。设单链表(不带头结点)头指针top指向第一个元素结点(栈顶),入栈操作是头插入,在栈顶结点之前插入结点,出栈操作是头删除,删除栈顶结点并返回栈顶元素,再让top指向新的栈顶结点。
package stack;
import dataStructure.SinglyList;
public final class LinkedStack<T> implements Stack<T> {
private SinglyList<T> list;
public LinkedStack() {
this.list=new SinglyList<T>();
}
public boolean isEmpty() {
return this.list.isEmpty();
}
public void push(T x) {
this.list.insert(0,x);
}
public T peek() {
return this.list.get(0);
}
public T pop() {
return this.list.remove(0);
}
}
栈的应用
一.栈是嵌套调用机制的实现基础
在一个函数体中调用另一个函数,称为函数的嵌套调用。例如,在main()函数中调用LinkedStack< T>,在LinkedStack< T>中调用SinglyList< T >,此时三个函数都在执行。根据嵌套规则,每个函数在执行完以后都要返回调用语句。此时最先返回的是SinglyList< T >,故而其为栈顶。
二.使用栈以非递归方式实现算法
1.括号匹配的语法检查
程序中出现的圆括号()以及其他括号都是左右匹配的,如果不匹配,就不能通过编译,编译器将给出错误信息。
以判断表达式中的圆括号是否匹配为例,使用栈判断括号匹配的问题的算法描述如下:
(1)设infix是一个表达式字符串,从左到右依次对infix的每个字符ch进行语法检查。若ch是左括号,则入栈;若ch是右括号,则栈里左括号出栈。若出栈为左括号,表示这一对括号匹配,如果栈空或者出栈字符不是左括号,表示缺少与ch匹配的左括号。
(2)重复执行(1),当infix检查结束,如果栈空,则全部匹配,如果还有左括号,表示缺少右括号。
当infix=“((1+2)*3+4)”的时候,对其分析,i=0时,‘(’入栈,当i=1时,‘(’入栈。当i=5时,ch=‘)’,出栈‘(’,匹配。当i=10时,ch=‘)’,出栈‘(’,匹配。此时栈空,全部匹配。
package stack;
public class Bracket {
//检查infix表达式中的圆括号是否匹配,若匹配,返回空串;否则返回错误信息
public static String isMatched(String infix) {
//声明接口对象stack,引用实现Stack<T>接口的顺序栈类的实例,创建空栈
Stack<String> stack=new SeqStack<String>(infix.length());
for(int i=0;i<infix.length();i++) {
char ch=infix.charAt(i);
switch(ch)
{
case'(':
stack.push(ch+"");
break;
case')':
if(!stack.isEmpty()) {
stack.pop();
}else {
return "期望(";
}
}
}
return (stack.isEmpty())?"无出错":"期望)"; //返回空串表示没有错误
}
public static void main(String[] args) {
String infix="((1+2)*3+4)";
System.out.println(infix+",编译错误:"+Bracket.isMatched(infix));
}
}
程序运行结果如下:
2.使用栈计算算术表达式的值
将运算符写在两个操作数中间的表达式,称为中缀表达式。中缀表达式中,运算符具有不同的优先级,将运算符写在两个操作数之前/后的表达式,分别称为前/后缀表达式。例如,将中缀表达式“1+2*(3-4)+5”转换为后缀表达式:1 2 3 4 - * + 5 +。后缀表达式中,运算符没有优先级,求值过程按照左到右进行。
(1)将中缀表达式转换为后缀表达式
将中缀表达式infix转换为后缀表达式字符串postfix,设置一个运算符栈,算法从左到右依次对中缀表达式中每个字符ch分别进行以下处理。
1.若ch是数字,则将其数字序列添加到postfix,添加空格作为分隔符。
2.若ch是运算符,将ch入栈。入栈之前,需将ch与栈顶运算符比较,如果栈顶运算符优先级较高或与ch相等,则出栈,添加到postfix,比较多次,当‘(’运算符在栈中,它的优先级最低。
3.若ch是左括号,入栈
4.若ch是右括号‘)’,则若干运算符出栈,直到出栈的是左括号,表示一对括号匹配。
5.表达式结束时,将栈中运算符全部出栈,添加到postfix。
我们以“1+2*(3-4)+5”为例子进行分析。
i=1时,‘+’入栈,后缀:1;i=3时, '* '入栈,后缀:1 2;i=4时,‘(’入栈,后缀:1 2;i=6时,‘-’入栈,后缀:1 2 3;i=8时,ch=‘)’,出栈‘-’、‘(’,后缀:1 2 3 4 -;i=9时,出栈 ‘*’、‘+’,‘+’入栈,后缀:1 2 3 4 - * +
(2)后缀表达式求值
后缀表达式求值算法描述如下,设置一个操作数栈,从左到右依次对postfix后缀表达式字符串中每个字符ch进行处理。
1.若ch是数字,先将其后数字序列转化为整数,再将该整数入栈。
2.若ch是运算符,出栈两个操作数进行运算,运算结果再入栈。
3.重复以上步骤,直至后缀表达式结束,栈中最后一个元素就是所求表达式的结果。
我们以“1 2 3 4 - * + 5 +”例子进行分析。
首先1,2,3,4直接入栈;当i=4时,ch=‘-’,4,3出栈,3-4=-1入栈;当i=5时,ch=‘*’,-1,2出栈,2 *(-1)=-2入栈;i=6时,ch=‘+’,-2,1出栈,1+(-2)=-1入栈;接着5入栈;i=8时,ch=‘+’,5,-1出栈,-1+5=4入栈。
程序如下,toPostfix()方法将中缀表达式转换为后缀表达式后返回;toValue()方法计算后缀表达式的值。两个栈中的数据类型不同,分别使用顺序栈和链式栈实现。
package stack;
public class Expression {
public static StringBuffer toPostfix(String infix) { //返回将infix中缀表达式转换成的后缀表达式
Stack<String> stack=new SeqStack<String>(infix.length()); //运算符栈,顺序栈
StringBuffer postfix=new StringBuffer(infix.length()*2); //后缀表达式字符串
int i=0;
while(i<infix.length()) {
char ch=infix.charAt(i);
switch(ch) {
case'+':case'-': //遇到+、-运算符
while(!stack.isEmpty()&&!stack.peek().equals("(")) //与栈顶运算符相比较
postfix.append(stack.pop()); //出栈运算符添加到后缀表达式串
stack.push(ch+""); //当前运算符入栈
i++;
break;
case'*':case'/': //遇到*、/运算符
while(!stack.isEmpty()&&(stack.peek().equals("*")||stack.peek().equals("/")))
postfix.append(stack.pop()); //栈顶优先级高的运算符出栈
stack.push(ch+"");
i++;
break;
case'(': //遇到左括号,直接入栈
stack.push(ch+"");
i++;
break;
case')':
String out=stack.pop(); //遇到右括号,出栈,若栈空返回null
while(out!=null&&!out.equals("(")) {
postfix.append(out);
out=stack.pop();
}
i++;
break;
default: //遇到数字,添加到后缀表达式
while(i<infix.length()&&ch>='0'&&ch<='9') {
postfix.append(ch);
i++;
if(i<infix.length())
ch=infix.charAt(i);
}
}
}
while(!stack.isEmpty())
postfix.append(stack.pop());
return postfix;
}
//计算后缀表达式的值
public static int toValue(StringBuffer postfix) {
Stack<Integer> stack=new LinkedStack<Integer>(); //操作数栈,链式栈
int value=0;
for(int i=0;i<postfix.length();i++) {
char ch=postfix.charAt(i);
if(ch>='0'&&ch<='9') { //遇到数字字符
value=0;
String s=String.valueOf(ch);
value=Integer.parseInt(s);
stack.push(value);
}else {
int y=stack.pop(),x=stack.pop(); //出栈两个操作数,注意出栈顺序
switch(ch) {
case'+':value=x+y; break;
case'-':value=x-y; break;
case'*':value=x*y; break;
case'/':value=x/y; break;
}
System.out.println(x+(ch+"")+y+"="+value+", "); //显示运算过程
stack.push(value);
}
}
return stack.pop(); //返回运算结果
}
public static void main(String[] args) {
String infix="1+2*(3-4)+5";
StringBuffer postfix=toPostfix(infix);
System.out.println("infix="+infix+"\npostfix="+postfix+"\nvalue="+toValue(postfix));
}
}
运行结果如下: